diff --git a/Procfile b/Procfile
index 2ade429cc39bab76da1f6a78b656de81c0c5e262..73185a111c03e0569356b05d0bca940fa03c9d49 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1 @@
-web: npm run heroku-start && node server
+web: npm run heroku-start
diff --git a/package-lock.json b/package-lock.json
index f5453db741002ed49a18a91931e47e9a92a701cb..47f2c39bdccc8bfcbf9db1acab50db04b772521c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1746,6 +1746,12 @@
       "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=",
       "dev": true
     },
+    "array-filter": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz",
+      "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=",
+      "dev": true
+    },
     "array-find-index": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@@ -1767,11 +1773,23 @@
         "es-abstract": "^1.7.0"
       }
     },
+    "array-map": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
+      "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=",
+      "dev": true
+    },
     "array-parallel": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz",
       "integrity": "sha1-j3hTCJJu1apHjEfmTRszS2wMlH0="
     },
+    "array-reduce": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz",
+      "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=",
+      "dev": true
+    },
     "array-series": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz",
@@ -8100,6 +8118,12 @@
       "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
       "dev": true
     },
+    "jsonify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+      "dev": true
+    },
     "jsonpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.0.0.tgz",
@@ -9014,6 +9038,12 @@
         }
       }
     },
+    "memorystream": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
+      "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=",
+      "dev": true
+    },
     "meow": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz",
@@ -10886,6 +10916,95 @@
         "which": "^1.2.10"
       }
     },
+    "npm-run-all": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz",
+      "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "chalk": "^2.4.1",
+        "cross-spawn": "^6.0.5",
+        "memorystream": "^0.3.1",
+        "minimatch": "^3.0.4",
+        "pidtree": "^0.3.0",
+        "read-pkg": "^3.0.0",
+        "shell-quote": "^1.6.1",
+        "string.prototype.padend": "^3.0.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        },
+        "cross-spawn": {
+          "version": "6.0.5",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+          "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+          "dev": true,
+          "requires": {
+            "nice-try": "^1.0.4",
+            "path-key": "^2.0.1",
+            "semver": "^5.5.0",
+            "shebang-command": "^1.2.0",
+            "which": "^1.2.9"
+          }
+        },
+        "load-json-file": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+          "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^4.0.0",
+            "pify": "^3.0.0",
+            "strip-bom": "^3.0.0"
+          }
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "path-type": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+          "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+          "dev": true,
+          "requires": {
+            "pify": "^3.0.0"
+          }
+        },
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        },
+        "read-pkg": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+          "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+          "dev": true,
+          "requires": {
+            "load-json-file": "^4.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^3.0.0"
+          }
+        }
+      }
+    },
     "npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -12807,6 +12926,12 @@
       "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
       "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
     },
+    "pidtree": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz",
+      "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==",
+      "dev": true
+    },
     "pify": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -14211,6 +14336,18 @@
       "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
       "dev": true
     },
+    "shell-quote": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz",
+      "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=",
+      "dev": true,
+      "requires": {
+        "array-filter": "~0.0.0",
+        "array-map": "~0.0.0",
+        "array-reduce": "~0.0.0",
+        "jsonify": "~0.0.0"
+      }
+    },
     "sigmund": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
@@ -15119,6 +15256,17 @@
         }
       }
     },
+    "string.prototype.padend": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz",
+      "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.2",
+        "es-abstract": "^1.4.3",
+        "function-bind": "^1.0.2"
+      }
+    },
     "string.prototype.trim": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz",
diff --git a/package.json b/package.json
index ee29ccf5f7258345c30d90b091cc0e9cf19fe7b7..347559e3d2ff5c9308d3c23b23f0aa76ead40124 100644
--- a/package.json
+++ b/package.json
@@ -57,41 +57,47 @@
     "xpath": "~0.0.27"
   },
   "scripts": {
-    "coverage:test:frontend": "BABEL_ENV=test nyc --nycrc-path .nycrc-frontend.json node_modules/mocha/bin/_mocha --opts mocha.opts --require @babel/polyfill --require @babel/register --require mocha-yaml-loader \"frontend/**/*.spec.js\"",
-    "coverage:test:server": "HANDLE_INTERNAL_ERRORS=false nyc node_modules/mocha/bin/_mocha --opts mocha.opts \"lib/**/*.spec.js\" \"services/**/*.spec.js\"",
-    "coverage:test:package": "nyc node_modules/mocha/bin/_mocha --opts mocha.opts \"gh-badges/**/*.spec.js\"",
-    "coverage:test:integration": "nyc node_modules/mocha/bin/_mocha --opts mocha.opts \"lib/**/*.integration.js\" \"services/**/*.integration.js\"",
-    "coverage:test:services": "nyc node_modules/mocha/bin/_mocha --opts mocha.opts --delay lib/service-test-runner/cli.js",
-    "coverage:test": "rimraf .nyc_output coverage; npm run coverage:test:server; npm run coverage:test:package; npm run coverage:test:frontend; npm run coverage:test:integration; npm run coverage:test:services",
-    "coverage:report": "nyc report",
-    "coverage:report:reopen": "opn coverage/lcov-report/index.html",
-    "coverage:report:open": "npm run coverage:report && npm run coverage:report:reopen",
+    "coverage:test:server": "nyc npm run test:server",
+    "coverage:test:frontend": "nyc --nycrc-path .nycrc-frontend.json npm run test:frontend",
+    "coverage:test:package": "nyc npm run test:package",
+    "coverage:test:integration": "nyc npm run test:integration",
+    "coverage:test:services": "nyc npm run test:services",
+    "coverage:clean": "rimraf .nyc_output coverage",
+    "precoverage:test": "run-s --silent coverage:clean defs features",
+    "coverage:test": "run-s --silent --continue-on-error coverage:test:server coverage:test:package coverage:test:frontend coverage:test:integration",
+    "coverage:report:generate": "nyc report",
+    "coverage:report:open": "opn coverage/lcov-report/index.html",
+    "coverage:report": "run-s --silent coverage:report:generate coverage:report:open",
     "lint": "eslint \"**/*.js\"",
     "prettier": "prettier --write \"**/*.js\"",
     "prettier-check": "prettier-check \"**/*.js\"",
     "danger": "danger",
-    "test:js:frontend": "BABEL_ENV=test mocha --opts mocha.opts --require @babel/polyfill --require @babel/register --require mocha-yaml-loader \"frontend/**/*.spec.js\"",
-    "test:js:server": "HANDLE_INTERNAL_ERRORS=false mocha --opts mocha.opts \"lib/**/*.spec.js\" \"services/**/*.spec.js\"",
-    "test:js:package": "mocha --opts mocha.opts \"gh-badges/**/*.spec.js\"",
+    "test:js:server": "echo \"Deprecated; run `npm run test:server` instead.\" && npm run test:server",
+    "test:js:frontend": "echo \"Deprecated; run `npm run test:frontend` instead.\" && npm run test:frontend",
+    "test:js:package": "echo \"Deprecated; run `npm run test:package` instead.\" && npm run test:package",
+    "test:frontend": "BABEL_ENV=test mocha --opts mocha.opts --require @babel/polyfill --require @babel/register --require mocha-yaml-loader \"frontend/**/*.spec.js\"",
+    "test:server": "HANDLE_INTERNAL_ERRORS=false mocha --opts mocha.opts \"lib/**/*.spec.js\" \"services/**/*.spec.js\"",
+    "test:package": "mocha --opts mocha.opts \"gh-badges/**/*.spec.js\"",
     "test:integration": "mocha --opts mocha.opts \"lib/**/*.integration.js\" \"services/**/*.integration.js\"",
     "test:services": "HANDLE_INTERNAL_ERRORS=false mocha --opts mocha.opts --delay lib/service-test-runner/cli.js",
-    "test:services:trace": "TRACE_SERVICES=true npm run test:services -- $*",
+    "test:services:trace": "TRACE_SERVICES=true run-s --silent test:services -- $*",
     "test:services:pr:prepare": "node lib/service-test-runner/pull-request-services-cli.js > pull-request-services.log",
     "test:services:pr:run": "HANDLE_INTERNAL_ERRORS=false mocha --opts mocha.opts --delay lib/service-test-runner/cli.js --stdin < pull-request-services.log",
-    "test:services:pr": "npm run test:services:pr:prepare && npm run test:services:pr:run",
-    "test": "npm run defs && npm run lint && npm run test:js:frontend && npm run test:js:package && npm run test:js:server",
+    "test:services:pr": "run-s --silent test:services:pr:prepare test:services:pr:run",
+    "pretest": "run-s --silent defs features",
+    "test": "run-s --silent --continue-on-error lint test:frontend test:package test:server prettier-check",
     "depcheck": "check-node-version --node \">= 8.0\"",
-    "postinstall": "npm run depcheck",
-    "prebuild": "npm run depcheck",
+    "postinstall": "run-s --silent depcheck",
+    "prebuild": "run-s --silent depcheck",
     "features": "node scripts/export-supported-features-cli.js > supported-features.json",
     "defs": "node scripts/export-service-definitions-cli.js > service-definitions.yml",
-    "build": "npm run defs && npm run features && BABEL_ENV=dev-prod next build && BABEL_ENV=dev-prod next export -o build/",
-    "heroku-postbuild": "npm run build",
-    "heroku-start": "node scripts/export-heroku-secrets-cli.js",
+    "build": "run-s --silent defs features && BABEL_ENV=dev-prod next build && BABEL_ENV=dev-prod next export -o build/",
+    "heroku-postbuild": "run-s --silent build",
     "analyze": "ANALYZE=true LONG_CACHE=false BASE_URL=https://img.shields.io npm run build",
     "start:server": "HANDLE_INTERNAL_ERRORS=false RATE_LIMIT=false node server 8080 ::",
     "now-start": "node server",
-    "prestart": "npm run depcheck && npm run defs && npm run features",
+    "heroku-start": "node scripts/export-heroku-secrets-cli.js && node server",
+    "prestart": "run-s --silent depcheck defs features",
     "start": "concurrently --names server,frontend \"ALLOWED_ORIGIN=http://localhost:3000 npm run start:server\" \"BASE_URL=http://[::]:8080 BABEL_ENV=dev-prod next dev\"",
     "refactoring-report": "node scripts/refactoring-cli.js"
   },
@@ -163,6 +169,7 @@
     "nock": "11.0.0-beta.2",
     "node-fetch": "^2.3.0",
     "node-mocks-http": "^1.7.3",
+    "npm-run-all": "^4.1.5",
     "nyc": "^13.0.1",
     "opn-cli": "^4.0.0",
     "portfinder": "^1.0.20",