Repository: badges/shields (js)

Issue:
Netlify status badge
:clipboard: **Description**

The popular CDN and static site host Netlify has added support for badges with build information (e.g., 
"success", "building" or "failed").

Badges can be added like: "netlify | success", just like the current "build | passing".

Details: https://www.netlify.com/docs/continuous-deployment/#status-badges

Example:

![Netlify badge example](https://img.shields.io/badge/netlify-success-brightgreen.svg)

:link: **Data**

A general API is available at https://www.netlify.com/docs/api/. This issue will be updated once the documentation for the particular build status API are published too.

:microphone: **Motivation**

Lots of websites are now using Netlify as opposed to GitHub Pages. According to their website, over 400,000 "developers trust Netlify", probably since it's free and has a great toolkit (e.g., Netlify CMS).

Since they have badges available now, they should be added to Shields.io too!

<!-- Love Shields? Please consider donating $10 to sustain our activities:
👉  https://opencollective.com/shields -->


Pull request:
Add [Netlify] (fixed #3129)
This PR adds a status badge for Netlify, the static CDN and fixes #3953.

I tried making a PR in #3178 from scratch by using a "closest color matcher" many months ago, but I wasn't able to complete it. This time, it uses the SVG scraping service, and works pretty well (as long as Netlify doesn't update their badge colors, after which we'll update the service).

Thanks for the community for being so patient!

Patch:
diff --git a/services/netlify/netlify.service.js b/services/netlify/netlify.service.js
new file mode 100644
index 0000000000..0d39e00cde
--- /dev/null
+++ b/services/netlify/netlify.service.js
@@ -0,0 +1,75 @@
+'use strict'
+
+const { renderBuildStatusBadge } = require('../build-status')
+const { BaseSvgScrapingService } = require('..')
+
+const pendingStatus = 'building'
+const notBuiltStatus = 'not built'
+
+const statusMap = {
+  building: pendingStatus,
+  waiting: pendingStatus,
+  initiated: pendingStatus,
+  stopped: notBuiltStatus,
+  ignored: notBuiltStatus,
+  blocked: notBuiltStatus,
+  infrastructure_failure: 'failed',
+}
+
+module.exports = class Netlify extends BaseSvgScrapingService {
+  static get category() {
+    return 'build'
+  }
+
+  static get route() {
+    return {
+      base: 'netlify',
+      pattern: ':projectId',
+    }
+  }
+
+  static get examples() {
+    return [
+      {
+        title: 'Netlify',
+        namedParams: {
+          projectId: 'e6d5a4e0-dee1-4261-833e-2f47f509c68f',
+        },
+        documentation:
+          'To locate your project id, visit your project settings, scroll to "Status badges" under "General", and copy the ID between "/api/v1/badges/" and "/deploy-status" in the code sample',
+        staticPreview: renderBuildStatusBadge({ status: 'passing' }),
+      },
+    ]
+  }
+
+  static get defaultBadgeData() {
+    return {
+      label: 'netlify',
+    }
+  }
+
+  static render({ status }) {
+    status = statusMap[status] || status
+    const result = renderBuildStatusBadge({ status })
+    if (result.message === 'building' && !result.color) {
+      result.color = 'yellow'
+    }
+    return result
+  }
+
+  async fetch({ projectId, branch }) {
+    const url = `https://api.netlify.com/api/v1/badges/${projectId}/deploy-status`
+    const { buffer } = await this._request({
+      url,
+    })
+    if (buffer.includes('#EAFAF9')) return { message: 'passing' }
+    if (buffer.includes('#FFF3F4')) return { message: 'failing' }
+    if (buffer.includes('#FEFAEA')) return { message: 'building' }
+    return { message: 'unknown' }
+  }
+
+  async handle({ projectId, branch }) {
+    const { message: status } = await this.fetch({ projectId, branch })
+    return this.constructor.render({ status })
+  }
+}
diff --git a/services/netlify/netlify.spec.js b/services/netlify/netlify.spec.js
new file mode 100644
index 0000000000..f35e439cd1
--- /dev/null
+++ b/services/netlify/netlify.spec.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const { test, given } = require('sazerac')
+const Netlify = require('./netlify.service')
+
+const building = { message: 'building', label: undefined, color: 'yellow' }
+const notBuilt = { message: 'not built', label: undefined, color: undefined }
+
+describe('Netlify', function() {
+  test(Netlify.render, () => {
+    given({ status: 'building' }).expect(building)
+    given({ status: 'stopped' }).expect(notBuilt)
+    given({ status: 'infrastructure_failure' }).expect({
+      message: 'failing',
+      color: 'red',
+      label: undefined,
+    })
+  })
+})


Test patch:
diff --git a/services/netlify/netlify.tester.js b/services/netlify/netlify.tester.js
new file mode 100644
index 0000000000..abea046119
--- /dev/null
+++ b/services/netlify/netlify.tester.js
@@ -0,0 +1,15 @@
+'use strict'
+
+const { isBuildStatus } = require('../build-status')
+const t = (module.exports = require('../tester').createServiceTester())
+
+t.create('netlify (valid, no branch)')
+  .get('/e6d5a4e0-dee1-4261-833e-2f47f509c68f.json')
+  .expectBadge({
+    label: 'netlify',
+    message: isBuildStatus,
+  })
+
+t.create('netlify (repo not found)')
+  .get('/not-a-repo.json')
+  .expectBadge({ label: 'netlify', message: 'not found' })