diff --git a/.prettierignore b/.prettierignore
index 65d17f6404dfab4257b0e5f16fb47898a32912df..dac86b5475f8fdbc4d58cbc37bc158c766996217 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,7 +1,3 @@
-server.js
-dangerfile.js
-next.config.js
-server.spec.js
 package.json
 package-lock.json
 /__snapshots__
diff --git a/dangerfile.js b/dangerfile.js
index 4722810c502e3014d6b75c62bd9a4a2fd21da0a5..8001a6c36bbb3bba482cbdaea1b52c590072840d 100644
--- a/dangerfile.js
+++ b/dangerfile.js
@@ -7,133 +7,147 @@
 // `./node_modules/.bin/danger pr pr-url`
 // Note that the line numbers in the runtime errors are incorrecr.
 
-const { danger, fail, message, warn } = require('danger');
-const chainsmoker = require('chainsmoker');
-const { default: noTestShortcuts } = require('danger-plugin-no-test-shortcuts');
+const { danger, fail, message, warn } = require('danger')
+const chainsmoker = require('chainsmoker')
+const { default: noTestShortcuts } = require('danger-plugin-no-test-shortcuts')
 
 const fileMatch = chainsmoker({
   created: danger.git.created_files,
   modified: danger.git.modified_files,
   createdOrModified: danger.git.modified_files.concat(danger.git.created_files),
   deleted: danger.git.deleted_files,
-});
+})
 
 const documentation = fileMatch(
   '**/*.md',
   'lib/all-badge-examples.js',
   'frontend/components/usage.js'
-);
-const server = fileMatch('server.js');
-const serviceTests = fileMatch('services/**/*.tester.js');
+)
+const server = fileMatch('server.js')
+const serviceTests = fileMatch('services/**/*.tester.js')
 const helpers = fileMatch(
   'lib/**/*.js',
   '!**/*.spec.js',
   '!lib/all-badge-examples.js'
-);
-const logos = fileMatch(
-  'logo/*.svg'
-);
-const helperTests = fileMatch('lib/**/*.spec.js');
-const packageJson = fileMatch('package.json');
-const packageLock = fileMatch('package-lock.json');
-const capitals = fileMatch('**/*[A-Z]*.js');
-const underscores = fileMatch('**/*_*.js');
-const targetBranch = danger.github.pr.base.ref;
-
-message([
-  ':sparkles: Thanks for your contribution to Shields, ',
-  `@${danger.github.pr.user.login}!`,
-].join(''));
+)
+const logos = fileMatch('logo/*.svg')
+const helperTests = fileMatch('lib/**/*.spec.js')
+const packageJson = fileMatch('package.json')
+const packageLock = fileMatch('package-lock.json')
+const capitals = fileMatch('**/*[A-Z]*.js')
+const underscores = fileMatch('**/*_*.js')
+const targetBranch = danger.github.pr.base.ref
+
+message(
+  [
+    ':sparkles: Thanks for your contribution to Shields, ',
+    `@${danger.github.pr.user.login}!`,
+  ].join('')
+)
 
 if (targetBranch !== 'master') {
-  const message = `This PR targets \`${targetBranch}\``;
-  const idea = 'It is likely that the target branch should be `master`';
-  warn(`${message} - <i>${idea}</i>`);
+  const message = `This PR targets \`${targetBranch}\``
+  const idea = 'It is likely that the target branch should be `master`'
+  warn(`${message} - <i>${idea}</i>`)
 }
 
 if (documentation.createdOrModified) {
-  message([
-    'Thanks for contributing to our documentation. ',
-    'We :heart: our [documentarians](http://www.writethedocs.org/)!',
-  ].join(''));
+  message(
+    [
+      'Thanks for contributing to our documentation. ',
+      'We :heart: our [documentarians](http://www.writethedocs.org/)!',
+    ].join('')
+  )
 }
 
 if (packageJson.modified && !packageLock.modified) {
-  const message = 'This PR modified `package.json`, but not `package-lock.json`';
-  const idea = 'Perhaps you need to run `npm install`?';
-  warn(`${message} - <i>${idea}</i>`);
+  const message = 'This PR modified `package.json`, but not `package-lock.json`'
+  const idea = 'Perhaps you need to run `npm install`?'
+  warn(`${message} - <i>${idea}</i>`)
 }
 
 if (server.modified && !serviceTests.createdOrModified) {
-  warn([
-    'This PR modified the server but none of the service tests. <br>',
-    "That's okay so long as it's refactoring existing code. ",
-    "Otherwise, please consider adding tests to the service: ",
-    "[How-to](https://github.com/badges/shields/blob/master/doc/service-tests.md#readme)",
-  ].join(''));
+  warn(
+    [
+      'This PR modified the server but none of the service tests. <br>',
+      "That's okay so long as it's refactoring existing code. ",
+      'Otherwise, please consider adding tests to the service: ',
+      '[How-to](https://github.com/badges/shields/blob/master/doc/service-tests.md#readme)',
+    ].join('')
+  )
 }
 
 if (helpers.created && !helperTests.created) {
-  warn([
-    'This PR added helper modules in `lib/` but not accompanying tests. <br>',
-    'Generally helper modules should have their own tests.',
-  ].join(''));
+  warn(
+    [
+      'This PR added helper modules in `lib/` but not accompanying tests. <br>',
+      'Generally helper modules should have their own tests.',
+    ].join('')
+  )
 } else if (helpers.createdOrModified && !helperTests.createdOrModified) {
-  warn([
-    'This PR modified helper functions in `lib/` but not accompanying tests. <br>',
-    "That's okay so long as it's refactoring existing code.",
-  ].join(''));
+  warn(
+    [
+      'This PR modified helper functions in `lib/` but not accompanying tests. <br>',
+      "That's okay so long as it's refactoring existing code.",
+    ].join('')
+  )
 }
 
 if (logos.created) {
-  message([
-    ':art: Thanks for submitting a logo. <br>',
-    'Please ensure your contribution follows our ',
-    '[guidance](https://github.com/badges/shields/blob/master/CONTRIBUTING.md#logos) ',
-    'for logo submissions.',
-  ].join(''));
+  message(
+    [
+      ':art: Thanks for submitting a logo. <br>',
+      'Please ensure your contribution follows our ',
+      '[guidance](https://github.com/badges/shields/blob/master/CONTRIBUTING.md#logos) ',
+      'for logo submissions.',
+    ].join('')
+  )
 }
 
 if (capitals.created || underscores.created) {
-  fail([
-    'JavaScript source files should be named with `kebab-case` ',
-    '(dash-separated lowercase).',
-  ].join(''));
+  fail(
+    [
+      'JavaScript source files should be named with `kebab-case` ',
+      '(dash-separated lowercase).',
+    ].join('')
+  )
 }
 
-const allFiles = danger.git.created_files.concat(danger.git.modified_files);
+const allFiles = danger.git.created_files.concat(danger.git.modified_files)
 
 allFiles.forEach(file => {
   danger.git.diffForFile(file).then(diff => {
     if (/\+.*assert[(.]/.test(diff.diff)) {
-      warn([
-        `Found 'assert' statement added in \`${file}\`. <br>`,
-        'Please ensure tests are written using Chai ',
-        '[expect syntax](http://chaijs.com/guide/styles/#expect)',
-      ].join(''));
+      warn(
+        [
+          `Found 'assert' statement added in \`${file}\`. <br>`,
+          'Please ensure tests are written using Chai ',
+          '[expect syntax](http://chaijs.com/guide/styles/#expect)',
+        ].join('')
+      )
     }
-  });
-});
+  })
+})
 
 function onlyUnique(value, index, self) {
-  return self.indexOf(value) === index;
+  return self.indexOf(value) === index
 }
 
 const affectedServices = allFiles
   .map(file => {
-    const match = file.match(/^services\/(.+)\/.+\.service.js$/);
-    return match ? match[1] : undefined;
+    const match = file.match(/^services\/(.+)\/.+\.service.js$/)
+    return match ? match[1] : undefined
   })
   .filter(Boolean)
-  .filter(onlyUnique);
+  .filter(onlyUnique)
 
 const testedServices = allFiles
   .map(file => {
-    const match = file.match(/^services\/(.+)\/.+\.tester.js$/);
-    return match ? match[1] : undefined;
+    const match = file.match(/^services\/(.+)\/.+\.tester.js$/)
+    return match ? match[1] : undefined
   })
   .filter(Boolean)
-  .filter(onlyUnique);
+  .filter(onlyUnique)
 
 affectedServices.forEach(service => {
   if (testedServices.indexOf(service) === -1) {
@@ -142,9 +156,9 @@ affectedServices.forEach(service => {
         `This PR modified service code for <kbd>${service}</kbd> but not its test code. <br>`,
         "That's okay so long as it's refactoring existing code.",
       ].join('')
-    );
+    )
   }
-});
+})
 
 // Prevent merging exclusive services tests.
 noTestShortcuts({
@@ -152,4 +166,4 @@ noTestShortcuts({
   patterns: {
     only: ['only()'],
   },
-});
+})
diff --git a/next.config.js b/next.config.js
index 0254924c5eddb910d92b8914b6e9e49205e3c97f..e20cf711fe59bde888dd5503b76375a26a44f69b 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,43 +1,47 @@
 'use strict'
 
-const envFlag = require('node-env-flag');
-const webpack = require('webpack');
-const shouldAnalyze = envFlag(process.env.ANALYZE);
-const assetPrefix = process.env.NEXT_ASSET_PREFIX;
+const envFlag = require('node-env-flag')
+const webpack = require('webpack')
+const shouldAnalyze = envFlag(process.env.ANALYZE)
+const assetPrefix = process.env.NEXT_ASSET_PREFIX
 
 module.exports = {
   webpack: config => {
-    config.plugins.push(new webpack.EnvironmentPlugin({ BASE_URL: null, LONG_CACHE: null }));
+    config.plugins.push(
+      new webpack.EnvironmentPlugin({ BASE_URL: null, LONG_CACHE: null })
+    )
 
     if (shouldAnalyze) {
       // We don't include webpack-bundle-analyzer in devDependencies, so  load
       // lazily.
-      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
-      config.plugins.push(new BundleAnalyzerPlugin({
-        analyzerMode: 'server',
-        analyzerPort: 8888,
-        openAnalyzer: true,
-      }));
+      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
+      config.plugins.push(
+        new BundleAnalyzerPlugin({
+          analyzerMode: 'server',
+          analyzerPort: 8888,
+          openAnalyzer: true,
+        })
+      )
     }
 
     config.module.loaders = (config.module.loaders || []).concat({
       test: /\.json$/,
       loader: 'json-loader',
-    });
+    })
 
     if (assetPrefix) {
-      config.output.publicPath = `${assetPrefix}/${config.output.publicPath}`;
+      config.output.publicPath = `${assetPrefix}/${config.output.publicPath}`
     }
 
-    return config;
+    return config
   },
   exportPathMap: () => ({
     '/': { page: '/' },
   }),
-};
+}
 
 // Avoid setting an `undefined` value. This causes
 // `TypeError: Cannot read property 'replace' of undefined` at build time.
 if (assetPrefix) {
-  module.exports.assetPrefix = assetPrefix;
+  module.exports.assetPrefix = assetPrefix
 }
diff --git a/server.js b/server.js
index 5fc2c48d3b431b97403d64fd49cd765ff2e87874..f15189f70bdc2bf3113730cbae5987430512e7f0 100644
--- a/server.js
+++ b/server.js
@@ -1,41 +1,41 @@
-'use strict';
-
-const { DOMParser } = require('xmldom');
-const jp = require('jsonpath');
-const path = require('path');
-const xpath = require('xpath');
-const yaml = require('js-yaml');
-const Raven = require('raven');
-
-const serverSecrets = require('./lib/server-secrets');
-Raven.config(process.env.SENTRY_DSN || serverSecrets.sentry_dsn).install();
-Raven.disableConsoleAlerts();
-
-const { loadServiceClasses } = require('./services');
-const { checkErrorResponse } = require('./lib/error-helper');
-const analytics = require('./lib/analytics');
-const config = require('./lib/server-config');
-const GithubConstellation = require('./services/github/github-constellation');
-const sysMonitor = require('./lib/sys/monitor');
-const log = require('./lib/log');
-const { makeMakeBadgeFn } = require('./lib/make-badge');
-const { QuickTextMeasurer } = require('./lib/text-measurer');
-const suggest = require('./lib/suggest');
+'use strict'
+
+const { DOMParser } = require('xmldom')
+const jp = require('jsonpath')
+const path = require('path')
+const xpath = require('xpath')
+const yaml = require('js-yaml')
+const Raven = require('raven')
+
+const serverSecrets = require('./lib/server-secrets')
+Raven.config(process.env.SENTRY_DSN || serverSecrets.sentry_dsn).install()
+Raven.disableConsoleAlerts()
+
+const { loadServiceClasses } = require('./services')
+const { checkErrorResponse } = require('./lib/error-helper')
+const analytics = require('./lib/analytics')
+const config = require('./lib/server-config')
+const GithubConstellation = require('./services/github/github-constellation')
+const sysMonitor = require('./lib/sys/monitor')
+const log = require('./lib/log')
+const { makeMakeBadgeFn } = require('./lib/make-badge')
+const { QuickTextMeasurer } = require('./lib/text-measurer')
+const suggest = require('./lib/suggest')
 const {
   makeColorB,
   makeLabel: getLabel,
   makeBadgeData: getBadgeData,
   setBadgeColor,
-} = require('./lib/badge-data');
+} = require('./lib/badge-data')
 const {
   makeHandleRequestFn,
   clearRequestCache,
-} = require('./lib/request-handler');
-const { clearRegularUpdateCache } = require('./lib/regular-update');
-const { makeSend } = require('./lib/result-sender');
-const { escapeFormat } = require('./lib/path-helpers');
+} = require('./lib/request-handler')
+const { clearRegularUpdateCache } = require('./lib/regular-update')
+const { makeSend } = require('./lib/result-sender')
+const { escapeFormat } = require('./lib/path-helpers')
 
-const serverStartTime = new Date((new Date()).toGMTString());
+const serverStartTime = new Date(new Date().toGMTString())
 
 const camp = require('camp').start({
   documentRoot: path.join(__dirname, 'public'),
@@ -44,274 +44,292 @@ const camp = require('camp').start({
   secure: config.ssl.isSecure,
   cert: config.ssl.cert,
   key: config.ssl.key,
-});
+})
 
 const githubConstellation = new GithubConstellation({
   persistence: config.persistence,
   service: config.services.github,
-});
-const { apiProvider: githubApiProvider } = githubConstellation;
+})
+const { apiProvider: githubApiProvider } = githubConstellation
 
 function reset() {
-  clearRequestCache();
-  clearRegularUpdateCache();
+  clearRequestCache()
+  clearRegularUpdateCache()
 }
 
 async function stop() {
-  await githubConstellation.stop();
-  analytics.cancelAutosaving();
+  await githubConstellation.stop()
+  analytics.cancelAutosaving()
   return new Promise(resolve => {
-    camp.close(resolve);
-  });
+    camp.close(resolve)
+  })
 }
 
 module.exports = {
   camp,
   reset,
   stop,
-};
+}
 
-log(`Server is starting up: ${config.baseUri}`);
+log(`Server is starting up: ${config.baseUri}`)
 
-let measurer;
+let measurer
 try {
-  measurer = new QuickTextMeasurer(config.font.path, config.font.fallbackPath);
+  measurer = new QuickTextMeasurer(config.font.path, config.font.fallbackPath)
 } catch (e) {
-  console.log(`Unable to load fallback font. Using Helvetica-Bold instead.`);
-  measurer = new QuickTextMeasurer('Helvetica');
+  console.log(`Unable to load fallback font. Using Helvetica-Bold instead.`)
+  measurer = new QuickTextMeasurer('Helvetica')
 }
-const makeBadge = makeMakeBadgeFn(measurer);
-const cache = makeHandleRequestFn(makeBadge);
+const makeBadge = makeMakeBadgeFn(measurer)
+const cache = makeHandleRequestFn(makeBadge)
 
-analytics.load();
-analytics.scheduleAutosaving();
-analytics.setRoutes(camp);
+analytics.load()
+analytics.scheduleAutosaving()
+analytics.setRoutes(camp)
 
 if (serverSecrets && serverSecrets.shieldsSecret) {
-  sysMonitor.setRoutes(camp);
+  sysMonitor.setRoutes(camp)
 }
 
-githubConstellation.initialize(camp);
+githubConstellation.initialize(camp)
 
-suggest.setRoutes(config.cors.allowedOrigin, githubApiProvider, camp);
+suggest.setRoutes(config.cors.allowedOrigin, githubApiProvider, camp)
 
 camp.notfound(/\.(svg|png|gif|jpg|json)/, (query, match, end, request) => {
-    const format = match[1];
-    const badgeData = getBadgeData("404", query);
-    badgeData.text[1] = 'badge not found';
-    badgeData.colorscheme = 'red';
-    // Add format to badge data.
-    badgeData.format = format;
-    const svg = makeBadge(badgeData);
-    makeSend(format, request.res, end)(svg);
-});
+  const format = match[1]
+  const badgeData = getBadgeData('404', query)
+  badgeData.text[1] = 'badge not found'
+  badgeData.colorscheme = 'red'
+  // Add format to badge data.
+  badgeData.format = format
+  const svg = makeBadge(badgeData)
+  makeSend(format, request.res, end)(svg)
+})
 
 camp.notfound(/.*/, (query, match, end, request) => {
-  end(null, { template: '404.html' });
-});
+  end(null, { template: '404.html' })
+})
 
 // Vendors.
 
-loadServiceClasses().forEach(
-  serviceClass => serviceClass.register(
+loadServiceClasses().forEach(serviceClass =>
+  serviceClass.register(
     { camp, handleRequest: cache, githubApiProvider },
-    { handleInternalErrors: config.handleInternalErrors }));
+    { handleInternalErrors: config.handleInternalErrors }
+  )
+)
 
 // User defined sources - JSON response
-camp.route(/^\/badge\/dynamic\/(json|xml|yaml)\.(svg|png|gif|jpg|json)$/,
-cache({
-  queryParams: ['uri', 'url', 'query', 'prefix', 'suffix'],
-  handler: function(query, match, sendBadge, request) {
-    const type = match[1];
-    const format = match[2];
-    const prefix = query.prefix || '';
-    const suffix = query.suffix || '';
-    const pathExpression = query.query;
-    let requestOptions = {};
-
-    const badgeData = getBadgeData('custom badge', query);
-
-    if (!query.uri && !query.url || !query.query){
-      setBadgeColor(badgeData, 'red');
-      badgeData.text[1] = !query.query ? 'no query specified' : 'no url specified';
-      sendBadge(format, badgeData);
-      return;
-    }
-
-    let url
-    try {
-      url = encodeURI(decodeURIComponent(query.url || query.uri));
-    } catch(e){
-      setBadgeColor(badgeData, 'red');
-      badgeData.text[1] = 'malformed url';
-      sendBadge(format, badgeData);
-      return;
-    }
-
-    switch (type) {
-      case 'json':
-        requestOptions = {
-          headers: {
-            Accept: 'application/json',
-          },
-          json: true,
-        };
-        break;
-      case 'xml':
-        requestOptions = {
-          headers: {
-            Accept: 'application/xml, text/xml',
-          },
-        };
-        break;
-      case 'yaml':
-        requestOptions = {
-          headers: {
-            Accept: 'text/x-yaml,  text/yaml, application/x-yaml, application/yaml, text/plain',
-          },
-        };
-        break;
-    }
+camp.route(
+  /^\/badge\/dynamic\/(json|xml|yaml)\.(svg|png|gif|jpg|json)$/,
+  cache({
+    queryParams: ['uri', 'url', 'query', 'prefix', 'suffix'],
+    handler: function(query, match, sendBadge, request) {
+      const type = match[1]
+      const format = match[2]
+      const prefix = query.prefix || ''
+      const suffix = query.suffix || ''
+      const pathExpression = query.query
+      let requestOptions = {}
+
+      const badgeData = getBadgeData('custom badge', query)
+
+      if ((!query.uri && !query.url) || !query.query) {
+        setBadgeColor(badgeData, 'red')
+        badgeData.text[1] = !query.query
+          ? 'no query specified'
+          : 'no url specified'
+        sendBadge(format, badgeData)
+        return
+      }
 
-    request(url, requestOptions, (err, res, data) => {
+      let url
       try {
-        if (checkErrorResponse(badgeData, err, res, { 404: 'resource not found' })) {
-          return;
-        }
-
-        badgeData.colorscheme = 'brightgreen';
-
-        let innerText = [];
-        switch (type){
-          case 'json':
-            data = (typeof data === 'object' ? data : JSON.parse(data));
-            data = jp.query(data, pathExpression);
-            if (!data.length) {
-              throw Error('no result');
-            }
-            innerText = data;
-            break;
-          case 'xml':
-            data = new DOMParser().parseFromString(data);
-            data = xpath.select(pathExpression, data);
-            if (!data.length) {
-              throw Error('no result');
-            }
-            data.forEach((i,v)=>{
-              innerText.push(pathExpression.indexOf('@') + 1 ? i.value : i.firstChild.data);
-            });
-            break;
-          case 'yaml':
-            data = yaml.safeLoad(data);
-            data = jp.query(data, pathExpression);
-            if (!data.length) {
-              throw Error('no result');
-            }
-            innerText = data;
-            break;
-        }
-        badgeData.text[1] = (prefix || '') + innerText.join(', ') + (suffix || '');
+        url = encodeURI(decodeURIComponent(query.url || query.uri))
       } catch (e) {
-        setBadgeColor(badgeData, 'lightgrey');
-        badgeData.text[1] = e.message;
-      } finally {
-        sendBadge(format, badgeData);
+        setBadgeColor(badgeData, 'red')
+        badgeData.text[1] = 'malformed url'
+        sendBadge(format, badgeData)
+        return
       }
-    });
-  },
-}));
-
-// Any badge.
-camp.route(/^\/(:|badge\/)(([^-]|--)*?)-?(([^-]|--)*)-(([^-]|--)+)\.(svg|png|gif|jpg)$/,
-(data, match, end, ask) => {
-  const subject = escapeFormat(match[2]);
-  const status = escapeFormat(match[4]);
-  const color = escapeFormat(match[6]);
-  const format = match[8];
 
-  analytics.noteRequest(data, match);
+      switch (type) {
+        case 'json':
+          requestOptions = {
+            headers: {
+              Accept: 'application/json',
+            },
+            json: true,
+          }
+          break
+        case 'xml':
+          requestOptions = {
+            headers: {
+              Accept: 'application/xml, text/xml',
+            },
+          }
+          break
+        case 'yaml':
+          requestOptions = {
+            headers: {
+              Accept:
+                'text/x-yaml,  text/yaml, application/x-yaml, application/yaml, text/plain',
+            },
+          }
+          break
+      }
 
-  // Cache management - the badge is constant.
-  const cacheDuration = (3600*24*1)|0;    // 1 day.
-  ask.res.setHeader('Cache-Control', 'max-age=' + cacheDuration);
-  if (+(new Date(ask.req.headers['if-modified-since'])) >= +serverStartTime) {
-    ask.res.statusCode = 304;
-    ask.res.end();  // not modified.
-    return;
-  }
-  ask.res.setHeader('Last-Modified', serverStartTime.toGMTString());
+      request(url, requestOptions, (err, res, data) => {
+        try {
+          if (
+            checkErrorResponse(badgeData, err, res, {
+              404: 'resource not found',
+            })
+          ) {
+            return
+          }
+
+          badgeData.colorscheme = 'brightgreen'
+
+          let innerText = []
+          switch (type) {
+            case 'json':
+              data = typeof data === 'object' ? data : JSON.parse(data)
+              data = jp.query(data, pathExpression)
+              if (!data.length) {
+                throw Error('no result')
+              }
+              innerText = data
+              break
+            case 'xml':
+              data = new DOMParser().parseFromString(data)
+              data = xpath.select(pathExpression, data)
+              if (!data.length) {
+                throw Error('no result')
+              }
+              data.forEach((i, v) => {
+                innerText.push(
+                  pathExpression.indexOf('@') + 1 ? i.value : i.firstChild.data
+                )
+              })
+              break
+            case 'yaml':
+              data = yaml.safeLoad(data)
+              data = jp.query(data, pathExpression)
+              if (!data.length) {
+                throw Error('no result')
+              }
+              innerText = data
+              break
+          }
+          badgeData.text[1] =
+            (prefix || '') + innerText.join(', ') + (suffix || '')
+        } catch (e) {
+          setBadgeColor(badgeData, 'lightgrey')
+          badgeData.text[1] = e.message
+        } finally {
+          sendBadge(format, badgeData)
+        }
+      })
+    },
+  })
+)
 
-  // Badge creation.
-  try {
-    const badgeData = getBadgeData(subject, data);
-    badgeData.text[0] = getLabel(subject, data);
-    badgeData.text[1] = status;
-    badgeData.colorB = makeColorB(color, data);
-    badgeData.template = data.style;
-    if (config.profiling.makeBadge) {
-      console.time('makeBadge total');
+// Any badge.
+camp.route(
+  /^\/(:|badge\/)(([^-]|--)*?)-?(([^-]|--)*)-(([^-]|--)+)\.(svg|png|gif|jpg)$/,
+  (data, match, end, ask) => {
+    const subject = escapeFormat(match[2])
+    const status = escapeFormat(match[4])
+    const color = escapeFormat(match[6])
+    const format = match[8]
+
+    analytics.noteRequest(data, match)
+
+    // Cache management - the badge is constant.
+    const cacheDuration = (3600 * 24 * 1) | 0 // 1 day.
+    ask.res.setHeader('Cache-Control', 'max-age=' + cacheDuration)
+    if (+new Date(ask.req.headers['if-modified-since']) >= +serverStartTime) {
+      ask.res.statusCode = 304
+      ask.res.end() // not modified.
+      return
     }
-    const svg = makeBadge(badgeData);
-    if (config.profiling.makeBadge) {
-      console.timeEnd('makeBadge total');
+    ask.res.setHeader('Last-Modified', serverStartTime.toGMTString())
+
+    // Badge creation.
+    try {
+      const badgeData = getBadgeData(subject, data)
+      badgeData.text[0] = getLabel(subject, data)
+      badgeData.text[1] = status
+      badgeData.colorB = makeColorB(color, data)
+      badgeData.template = data.style
+      if (config.profiling.makeBadge) {
+        console.time('makeBadge total')
+      }
+      const svg = makeBadge(badgeData)
+      if (config.profiling.makeBadge) {
+        console.timeEnd('makeBadge total')
+      }
+      makeSend(format, ask.res, end)(svg)
+    } catch (e) {
+      log.error(e.stack)
+      const svg = makeBadge({
+        text: ['error', 'bad badge'],
+        colorscheme: 'red',
+      })
+      makeSend(format, ask.res, end)(svg)
     }
-    makeSend(format, ask.res, end)(svg);
-  } catch(e) {
-    log.error(e.stack);
-    const svg = makeBadge({ text: ['error', 'bad badge'], colorscheme: 'red' });
-    makeSend(format, ask.res, end)(svg);
   }
-});
+)
 
 // Production cache debugging.
-let bitFlip = false;
+let bitFlip = false
 camp.route(/^\/flip\.svg$/, (data, match, end, ask) => {
-  const cacheSecs = 60;
-  ask.res.setHeader('Cache-Control', 'max-age=' + cacheSecs);
-  const reqTime = new Date();
-  const date = (new Date(+reqTime + cacheSecs * 1000)).toGMTString();
-  ask.res.setHeader('Expires', date);
-  const badgeData = getBadgeData('flip', data);
-  bitFlip = !bitFlip;
-  badgeData.text[1] = bitFlip? 'on': 'off';
-  badgeData.colorscheme = bitFlip? 'brightgreen': 'red';
-  const svg = makeBadge(badgeData);
-  makeSend('svg', ask.res, end)(svg);
-});
+  const cacheSecs = 60
+  ask.res.setHeader('Cache-Control', 'max-age=' + cacheSecs)
+  const reqTime = new Date()
+  const date = new Date(+reqTime + cacheSecs * 1000).toGMTString()
+  ask.res.setHeader('Expires', date)
+  const badgeData = getBadgeData('flip', data)
+  bitFlip = !bitFlip
+  badgeData.text[1] = bitFlip ? 'on' : 'off'
+  badgeData.colorscheme = bitFlip ? 'brightgreen' : 'red'
+  const svg = makeBadge(badgeData)
+  makeSend('svg', ask.res, end)(svg)
+})
 
 // Any badge, old version.
-camp.route(/^\/([^/]+)\/(.+).png$/,
-(data, match, end, ask) => {
-  const subject = match[1];
-  const status = match[2];
-  const color = data.color;
+camp.route(/^\/([^/]+)\/(.+).png$/, (data, match, end, ask) => {
+  const subject = match[1]
+  const status = match[2]
+  const color = data.color
 
   // Cache management - the badge is constant.
-  const cacheDuration = (3600*24*1)|0;    // 1 day.
-  ask.res.setHeader('Cache-Control', 'max-age=' + cacheDuration);
-  if (+(new Date(ask.req.headers['if-modified-since'])) >= +serverStartTime) {
-    ask.res.statusCode = 304;
-    ask.res.end();  // not modified.
-    return;
+  const cacheDuration = (3600 * 24 * 1) | 0 // 1 day.
+  ask.res.setHeader('Cache-Control', 'max-age=' + cacheDuration)
+  if (+new Date(ask.req.headers['if-modified-since']) >= +serverStartTime) {
+    ask.res.statusCode = 304
+    ask.res.end() // not modified.
+    return
   }
-  ask.res.setHeader('Last-Modified', serverStartTime.toGMTString());
+  ask.res.setHeader('Last-Modified', serverStartTime.toGMTString())
 
   // Badge creation.
   try {
-    const badgeData = { text: [subject, status] };
-    badgeData.colorscheme = color;
-    const svg = makeBadge(badgeData);
-    makeSend('png', ask.res, end)(svg);
-  } catch(e) {
-    const svg = makeBadge({ text: ['error', 'bad badge'], colorscheme: 'red' });
-    makeSend('png', ask.res, end)(svg);
+    const badgeData = { text: [subject, status] }
+    badgeData.colorscheme = color
+    const svg = makeBadge(badgeData)
+    makeSend('png', ask.res, end)(svg)
+  } catch (e) {
+    const svg = makeBadge({ text: ['error', 'bad badge'], colorscheme: 'red' })
+    makeSend('png', ask.res, end)(svg)
   }
-});
+})
 
 if (config.redirectUri) {
   camp.route(/^\/$/, (data, match, end, ask) => {
-    ask.res.statusCode = 302;
-    ask.res.setHeader('Location', config.redirectUri);
-    ask.res.end();
-  });
+    ask.res.statusCode = 302
+    ask.res.setHeader('Location', config.redirectUri)
+    ask.res.end()
+  })
 }
diff --git a/server.spec.js b/server.spec.js
index 23fd41a34613bc97718c485d4210a2e07a45f11e..c5d0913a638bb671012b9f0cd30a33f0b1d0a66d 100644
--- a/server.spec.js
+++ b/server.spec.js
@@ -1,95 +1,102 @@
-'use strict';
+'use strict'
 
-const { expect } = require('chai');
-const config = require('./lib/test-config');
-const fetch = require('node-fetch');
-const fs = require('fs');
-const isPng = require('is-png');
-const isSvg = require('is-svg');
-const path = require('path');
-const serverHelpers = require('./lib/in-process-server-test-helpers');
-const sinon = require('sinon');
-const svg2img = require('./lib/svg-to-img');
+const { expect } = require('chai')
+const config = require('./lib/test-config')
+const fetch = require('node-fetch')
+const fs = require('fs')
+const isPng = require('is-png')
+const isSvg = require('is-svg')
+const path = require('path')
+const serverHelpers = require('./lib/in-process-server-test-helpers')
+const sinon = require('sinon')
+const svg2img = require('./lib/svg-to-img')
 
-describe('The server', function () {
-  const baseUri = `http://127.0.0.1:${config.port}`;
+describe('The server', function() {
+  const baseUri = `http://127.0.0.1:${config.port}`
 
-  let server;
-  before('Start running the server', function () {
-    this.timeout(5000);
-    server = serverHelpers.start();
-  });
-  after('Shut down the server', function () { serverHelpers.stop(server); });
+  let server
+  before('Start running the server', function() {
+    this.timeout(5000)
+    server = serverHelpers.start()
+  })
+  after('Shut down the server', function() {
+    serverHelpers.stop(server)
+  })
 
-  it('should produce colorscheme badges', async function () {
+  it('should produce colorscheme badges', async function() {
     // This is the first server test to run, and often times out.
-    this.timeout(5000);
-    const res = await fetch(`${baseUri}/:fruit-apple-green.svg`);
-    expect(res.ok).to.be.true;
+    this.timeout(5000)
+    const res = await fetch(`${baseUri}/:fruit-apple-green.svg`)
+    expect(res.ok).to.be.true
     expect(await res.text())
       .to.satisfy(isSvg)
       .and.to.include('fruit')
-      .and.to.include('apple');
-  });
+      .and.to.include('apple')
+  })
 
-  it('should produce colorscheme PNG badges', async function () {
-    this.timeout(5000);
-    const res = await fetch(`${baseUri}/:fruit-apple-green.png`);
-    expect(res.ok).to.be.true;
-    expect(await res.buffer()).to.satisfy(isPng);
-  });
+  it('should produce colorscheme PNG badges', async function() {
+    this.timeout(5000)
+    const res = await fetch(`${baseUri}/:fruit-apple-green.png`)
+    expect(res.ok).to.be.true
+    expect(await res.buffer()).to.satisfy(isPng)
+  })
 
   // https://github.com/badges/shields/pull/1319
-  it('should not crash with a numeric logo', async function () {
-    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?logo=1`);
-    expect(res.ok).to.be.true;
+  it('should not crash with a numeric logo', async function() {
+    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?logo=1`)
+    expect(res.ok).to.be.true
     expect(await res.text())
       .to.satisfy(isSvg)
       .and.to.include('fruit')
-      .and.to.include('apple');
-  });
+      .and.to.include('apple')
+  })
 
-  it('should not crash with a numeric link', async function () {
-    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?link=1`);
-    expect(res.ok).to.be.true;
+  it('should not crash with a numeric link', async function() {
+    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?link=1`)
+    expect(res.ok).to.be.true
     expect(await res.text())
       .to.satisfy(isSvg)
       .and.to.include('fruit')
-      .and.to.include('apple');
-  });
+      .and.to.include('apple')
+  })
 
-  it('should not crash with a boolean link', async function () {
-    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?link=true`);
-    expect(res.ok).to.be.true;
+  it('should not crash with a boolean link', async function() {
+    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?link=true`)
+    expect(res.ok).to.be.true
     expect(await res.text())
       .to.satisfy(isSvg)
       .and.to.include('fruit')
-      .and.to.include('apple');
-  });
+      .and.to.include('apple')
+  })
 
-  context('with svg2img error', function () {
-    const expectedError = fs.readFileSync(path.resolve(__dirname, 'public', '500.html'));
+  context('with svg2img error', function() {
+    const expectedError = fs.readFileSync(
+      path.resolve(__dirname, 'public', '500.html')
+    )
 
-    let toBufferStub;
-    beforeEach(function () {
-      toBufferStub = sinon.stub(svg2img._imageMagick.prototype, 'toBuffer')
-        .callsArgWith(1, Error('whoops'));
-    });
-    afterEach(function () { toBufferStub.restore(); });
+    let toBufferStub
+    beforeEach(function() {
+      toBufferStub = sinon
+        .stub(svg2img._imageMagick.prototype, 'toBuffer')
+        .callsArgWith(1, Error('whoops'))
+    })
+    afterEach(function() {
+      toBufferStub.restore()
+    })
 
-    it('should emit the 500 message', async function () {
-      const res = await fetch(`${baseUri}/:some_new-badge-green.png`);
+    it('should emit the 500 message', async function() {
+      const res = await fetch(`${baseUri}/:some_new-badge-green.png`)
       // This emits status code 200, though 500 would be preferable.
-      expect(res.status).to.equal(200);
-      expect(await res.text()).to.include(expectedError);
-    });
-  });
+      expect(res.status).to.equal(200)
+      expect(await res.text()).to.include(expectedError)
+    })
+  })
 
-  describe('analytics endpoint', function () {
-    it('should return analytics in the expected format', async function () {
-      const res = await fetch(`${baseUri}/$analytics/v1`);
-      expect(res.ok).to.be.true;
-      const json = await res.json();
+  describe('analytics endpoint', function() {
+    it('should return analytics in the expected format', async function() {
+      const res = await fetch(`${baseUri}/$analytics/v1`)
+      expect(res.ok).to.be.true
+      const json = await res.json()
       const expectedKeys = [
         'vendorMonthly',
         'rawMonthly',
@@ -97,12 +104,14 @@ describe('The server', function () {
         'rawFlatMonthly',
         'vendorFlatSquareMonthly',
         'rawFlatSquareMonthly',
-      ];
-      expect(json).to.have.all.keys(...expectedKeys);
+      ]
+      expect(json).to.have.all.keys(...expectedKeys)
 
       Object.values(json).forEach(stats => {
-        expect(stats).to.be.an('array').with.length(36);
-      });
-    });
-  });
-});
+        expect(stats)
+          .to.be.an('array')
+          .with.length(36)
+      })
+    })
+  })
+})