From 2dd89e6c66fb7c4bbfda140cf3d8dca92b961e2c Mon Sep 17 00:00:00 2001 From: Thaddee Tyl Date: Tue, 9 Sep 2014 21:11:10 +0200 Subject: [PATCH] Badge for required node version from npm Fixes #237. --- server.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- try.html | 4 +++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 5cb88fc..81aac94 100644 --- a/server.js +++ b/server.js @@ -524,7 +524,7 @@ cache(function(data, match, sendBadge) { }); })); -// npm integration. +// npm download integration. camp.route(/^\/npm\/dm\/(.*)\.(svg|png|gif|jpg)$/, cache(function(data, match, sendBadge) { var user = match[1]; // eg, `localeval`. @@ -619,6 +619,57 @@ cache(function(data, match, sendBadge) { }); })); +// npm node version integration. +camp.route(/^\/node\/v\/(.*)\.(svg|png|gif|jpg)$/, +cache(function(data, match, sendBadge) { + var repo = match[1]; // eg, `localeval`. + var format = match[2]; + var apiUrl = 'https://registry.npmjs.org/' + repo + '/latest'; + var badgeData = getBadgeData('node', data); + // Using the Accept header because of this bug: + // + request(apiUrl, { headers: { 'Accept': '*/*' } }, function(err, res, buffer) { + if (err != null) { + badgeData.text[1] = 'inaccessible'; + sendBadge(format, badgeData); + } + try { + var data = JSON.parse(buffer); + if (data.engines && data.engines.node) { + var versionRange = data.engines.node; + badgeData.text[1] = versionRange; + var AGE_OLD = 1, AGE_CURRENT = 2, AGE_BLEEDING = 3; + regularUpdate('http://nodejs.org/dist/latest/SHASUMS.txt', + (24 * 3600 * 1000), + function(shasums) { + var firstLine = shasums.slice(0, shasums.indexOf('\n')); + var version = firstLine.split(' ')[1].split('-')[1]; + if (semver.satisfies(version, versionRange)) { + return AGE_CURRENT; + } else if (semver.gtr(version, versionRange)) { + return AGE_OLD; + } else { return AGE_BLEEDING; } + }, function(err, age) { + if (err != null) { sendBadge(format, badgeData); return; } + if (age === AGE_CURRENT) { + badgeData.colorscheme = 'brightgreen'; + } else if (age === AGE_OLD) { + badgeData.colorscheme = 'yellow'; + } else if (age === AGE_BLEEDING) { + badgeData.colorscheme = 'orange'; + } + sendBadge(format, badgeData); + }); + } else { + sendBadge(format, badgeData); + } + } catch(e) { + badgeData.text[1] = 'invalid'; + sendBadge(format, badgeData); + } + }); +})); + // Gem version integration. camp.route(/^\/gem\/v\/(.*)\.(svg|png|gif|jpg)$/, cache(function(data, match, sendBadge) { @@ -1904,6 +1955,34 @@ function streamFromString(str) { return newStream; } +// Map from URL to { timestamp: last fetch time, interval: in milliseconds, +// data: data }. +var regularUpdateCache = Object.create(null); +// url: a string, scraper: a function that takes string data at that URL. +// interval: number in milliseconds. +// cb: a callback function that takes an error and data returned by the scraper. +function regularUpdate(url, interval, scraper, cb) { + var timestamp = Date.now(); + var cache = regularUpdateCache[url]; + if (cache != null && + (timestamp - cache.timestamp) < interval) { + cb(null, regularUpdateCache[url].data); + return; + } + request(url, function(err, res, buffer) { + if (err != null) { cb(err); return; } + if (regularUpdateCache[url] == null) { + regularUpdateCache[url] = { timestamp: 0, data: 0 }; + } + try { + var data = scraper(buffer); + } catch(e) { cb(e); return; } + regularUpdateCache[url].timestamp = timestamp; + regularUpdateCache[url].data = data; + cb(null, data); + }); +} + // Given a number, string with appropriate unit in the metric system, SI. // Note: numbers beyond the peta- cannot be represented as integers in JS. var metricPrefix = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; diff --git a/try.html b/try.html index ef21a01..eabea4b 100644 --- a/try.html +++ b/try.html @@ -282,6 +282,10 @@ I made the GitHub Badge Service. http://img.shields.io/npm/v/npm.svg + node: + + http://img.shields.io/node/v/gh-badges.svg + PyPI: http://img.shields.io/pypi/v/nine.svg