From 4b23c63b2a9464958e73e6345ff7bfbafa0d3639 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Thu, 22 Feb 2018 07:43:53 -0700 Subject: [PATCH 1/7] support a route prefix, useful if you need to path route traffic behind a reverse proxy --- app.json | 5 ++++- bin/web.js | 1 + lib/nuts.js | 45 ++++++++++++++++++++++++++------------------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/app.json b/app.json index fa7b54d..239c959 100644 --- a/app.json +++ b/app.json @@ -16,6 +16,9 @@ }, "API_PASSWORD": { "description": "Password for private API access" + }, + "ROUTE_PREFIX": { + "description": "Custom prefix for all routes, defaults to /" } } -} \ No newline at end of file +} diff --git a/bin/web.js b/bin/web.js index 9e7c0aa..38e54bf 100644 --- a/bin/web.js +++ b/bin/web.js @@ -18,6 +18,7 @@ if (process.env.ANALYTICS_TOKEN) { } var myNuts = nuts.Nuts({ + routePrefix: process.env.ROUTE_PREFIX, repository: process.env.GITHUB_REPO, token: process.env.GITHUB_TOKEN, endpoint: process.env.GITHUB_ENDPOINT, diff --git a/lib/nuts.js b/lib/nuts.js index 1642158..8914283 100644 --- a/lib/nuts.js +++ b/lib/nuts.js @@ -35,9 +35,16 @@ function Nuts(opts) { preFetch: true, // Secret for GitHub webhook - refreshSecret: 'secret' + refreshSecret: 'secret', + + // Prefix for all routes + routePrefix: '/' }); + if (this.opts.routePrefix.substr(this.opts.routePrefix.length - 1, 1) !== '/') { + throw new Error('ROUTE_PREIX must end with a slash'); + } + // .init() is now a memoized version of ._init() this.init = _.memoize(this._init); @@ -51,26 +58,26 @@ function Nuts(opts) { // Bind routes this.router.use(useragent.express()); - this.router.get('/', this.onDownload); - this.router.get('/download/channel/:channel/:platform?', this.onDownload); - this.router.get('/download/version/:tag/:platform?', this.onDownload); - this.router.get('/download/:tag/:filename', this.onDownload); - this.router.get('/download/:platform?', this.onDownload); + this.router.get(`${that.opts.routePrefix}`, this.onDownload); + this.router.get(`${that.opts.routePrefix}download/channel/:channel/:platform?`, this.onDownload); + this.router.get(`${that.opts.routePrefix}download/version/:tag/:platform?`, this.onDownload); + this.router.get(`${that.opts.routePrefix}download/:tag/:filename`, this.onDownload); + this.router.get(`${that.opts.routePrefix}download/:platform?`, this.onDownload); - this.router.get('/feed/channel/:channel.atom', this.onServeVersionsFeed); + this.router.get(`${that.opts.routePrefix}feed/channel/:channel.atom`, this.onServeVersionsFeed); - this.router.get('/update', this.onUpdateRedirect); - this.router.get('/update/:platform/:version', this.onUpdate); - this.router.get('/update/channel/:channel/:platform/:version', this.onUpdate); - this.router.get('/update/:platform/:version/RELEASES', this.onUpdateWin); - this.router.get('/update/channel/:channel/:platform/:version/RELEASES', this.onUpdateWin); + this.router.get(`${that.opts.routePrefix}update`, this.onUpdateRedirect); + this.router.get(`${that.opts.routePrefix}update/:platform/:version`, this.onUpdate); + this.router.get(`${that.opts.routePrefix}update/channel/:channel/:platform/:version`, this.onUpdate); + this.router.get(`${that.opts.routePrefix}update/:platform/:version/RELEASES`, this.onUpdateWin); + this.router.get(`${that.opts.routePrefix}update/channel/:channel/:platform/:version/RELEASES`, this.onUpdateWin); - this.router.get('/notes/:version?', this.onServeNotes); + this.router.get(`${that.opts.routePrefix}notes/:version?`, this.onServeNotes); // Bind API - this.router.use('/api', this.onAPIAccessControl); + this.router.use(`${that.opts.routePrefix}api`, this.onAPIAccessControl); _.each(API_METHODS, function(method, route) { - this.router.get('/api/' + route, function(req, res, next) { + this.router.get(`${that.opts.routePrefix}api/${route}`, function(req, res, next) { return Q() .then(function() { return method.call(that, req); @@ -202,7 +209,7 @@ Nuts.prototype.onUpdateRedirect = function(req, res, next) { if (!req.query.version) throw new Error('Requires "version" parameter'); if (!req.query.platform) throw new Error('Requires "platform" parameter'); - return res.redirect('/update/'+req.query.platform+'/'+req.query.version); + return res.redirect(`${this.opts.routePrefix}update/${req.query.platform}/${req.query.version}`); }) .fail(next); }; @@ -241,7 +248,7 @@ Nuts.prototype.onUpdate = function(req, res, next) { console.error(latest.tag); var gitFilePath = (channel === '*' ? '/../../../' : '/../../../../../'); res.status(200).send({ - "url": urljoin(fullUrl, gitFilePath, '/download/version/'+latest.tag+'/'+platform+'?filetype='+filetype), + "url": urljoin(fullUrl, gitFilePath, `${this.opts.routePrefix}download/version/${latest.tag}/${platform}?filetype=${filetype}`), "name": latest.tag, "notes": releaseNotes, "pub_date": latest.published_at.toISOString() @@ -291,7 +298,7 @@ Nuts.prototype.onUpdateWin = function(req, res, next) { // Change filename to use download proxy .map(function(entry) { var gitFilePath = (channel === '*' ? '../../../../' : '../../../../../../'); - entry.filename = urljoin(fullUrl, gitFilePath, '/download/'+entry.semver+'/'+entry.filename); + entry.filename = urljoin(fullUrl, gitFilePath, `${that.opts.routePrefix}download/${entry.semver}/${entry.filename}`); return entry; }) @@ -366,7 +373,7 @@ Nuts.prototype.onServeVersionsFeed = function(req, res, next) { _.each(versions, function(version) { feed.addItem({ title: version.tag, - link: urljoin(fullUrl, '/../../../', '/download/version/'+version.tag), + link: urljoin(fullUrl, '/../../../', `download/version/${version.tag}`), description: version.notes, date: version.published_at, author: [] From b5165e95a5a0a1cba010c1257900e99829ab98d9 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Thu, 22 Feb 2018 07:45:10 -0700 Subject: [PATCH 2/7] rework Dockerfile, use official base image for node, use dumb-init for proper signal handling, zombie reaping, structure instructions in a more cachable way --- Dockerfile | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2438298..dded269 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,20 @@ -FROM mhart/alpine-node:5.8.0 +FROM node:6-alpine -# Switch to /app +# Setup Container WORKDIR /app +ENTRYPOINT ["/usr/local/bin/dumb-init", "--"] +CMD ["npm", "start"] +EXPOSE 80 + +# Dumb-init, proper signal handling, and zombie reaping +ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64 /usr/local/bin/dumb-init + # Install deps -COPY package.json ./ +COPY package.json /app/package.json RUN npm install --production -# Copy source -COPY . ./ + +# Copy Source +COPY . /app # Ports ENV PORT 80 -EXPOSE 80 - -ENTRYPOINT ["npm", "start"] From d180a70417083f3f45f635e17df465c6098bc122 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Thu, 22 Feb 2018 08:15:04 -0700 Subject: [PATCH 3/7] fix execute bit on dumb-init --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index dded269..d057241 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ EXPOSE 80 # Dumb-init, proper signal handling, and zombie reaping ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64 /usr/local/bin/dumb-init +RUN chmod +x /usr/local/bin/dumb-init # Install deps COPY package.json /app/package.json From 58a2d06d7e2fd438105fb02c1b6484edee46d839 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Thu, 22 Feb 2018 08:22:21 -0700 Subject: [PATCH 4/7] fix two this/that references --- lib/nuts.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/nuts.js b/lib/nuts.js index 8914283..43ab1ef 100644 --- a/lib/nuts.js +++ b/lib/nuts.js @@ -204,12 +204,14 @@ Nuts.prototype.onDownload = function(req, res, next) { // Request to update Nuts.prototype.onUpdateRedirect = function(req, res, next) { + var that = this; + Q() .then(function() { if (!req.query.version) throw new Error('Requires "version" parameter'); if (!req.query.platform) throw new Error('Requires "platform" parameter'); - return res.redirect(`${this.opts.routePrefix}update/${req.query.platform}/${req.query.version}`); + return res.redirect(`${that.opts.routePrefix}update/${req.query.platform}/${req.query.version}`); }) .fail(next); }; @@ -248,7 +250,7 @@ Nuts.prototype.onUpdate = function(req, res, next) { console.error(latest.tag); var gitFilePath = (channel === '*' ? '/../../../' : '/../../../../../'); res.status(200).send({ - "url": urljoin(fullUrl, gitFilePath, `${this.opts.routePrefix}download/version/${latest.tag}/${platform}?filetype=${filetype}`), + "url": urljoin(fullUrl, gitFilePath, `${that.opts.routePrefix}download/version/${latest.tag}/${platform}?filetype=${filetype}`), "name": latest.tag, "notes": releaseNotes, "pub_date": latest.published_at.toISOString() From 3089452c2557864979dc91d07a223775b4852516 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Thu, 22 Feb 2018 08:30:11 -0700 Subject: [PATCH 5/7] fix a double prefix issue --- lib/nuts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nuts.js b/lib/nuts.js index 43ab1ef..9219d79 100644 --- a/lib/nuts.js +++ b/lib/nuts.js @@ -250,7 +250,7 @@ Nuts.prototype.onUpdate = function(req, res, next) { console.error(latest.tag); var gitFilePath = (channel === '*' ? '/../../../' : '/../../../../../'); res.status(200).send({ - "url": urljoin(fullUrl, gitFilePath, `${that.opts.routePrefix}download/version/${latest.tag}/${platform}?filetype=${filetype}`), + "url": urljoin(fullUrl, gitFilePath, `download/version/${latest.tag}/${platform}?filetype=${filetype}`), "name": latest.tag, "notes": releaseNotes, "pub_date": latest.published_at.toISOString() @@ -300,7 +300,7 @@ Nuts.prototype.onUpdateWin = function(req, res, next) { // Change filename to use download proxy .map(function(entry) { var gitFilePath = (channel === '*' ? '../../../../' : '../../../../../../'); - entry.filename = urljoin(fullUrl, gitFilePath, `${that.opts.routePrefix}download/${entry.semver}/${entry.filename}`); + entry.filename = urljoin(fullUrl, gitFilePath, `download/${entry.semver}/${entry.filename}`); return entry; }) From 46f25af30971ad0e3d03ef4700ea91bb5a1d9847 Mon Sep 17 00:00:00 2001 From: Ben Williams Date: Wed, 17 Mar 2021 01:35:49 -0700 Subject: [PATCH 6/7] prettier --- .eslintrc | 21 +- .prettierrc | 4 + app.json | 42 +-- bin/web.js | 249 +++++++------ book.js | 36 +- lib/api.js | 70 ++-- lib/backends/backend.js | 199 +++++----- lib/backends/github.js | 144 +++---- lib/backends/index.js | 14 +- lib/index.js | 14 +- lib/nuts.js | 762 +++++++++++++++++++++----------------- lib/utils/notes.js | 44 +-- lib/utils/platforms.js | 248 +++++++------ lib/utils/win-releases.js | 175 +++++---- lib/versions.js | 243 ++++++------ test/platforms.js | 383 ++++++++++--------- test/win-releases.js | 212 +++++------ 17 files changed, 1499 insertions(+), 1361 deletions(-) create mode 100644 .prettierrc diff --git a/.eslintrc b/.eslintrc index 7330839..83f7107 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,18 +1,7 @@ { - "rules": { - "indent": [ 2, 4 ], - "quotes": [ 2, "single" ], - "linebreak-style": [ 2, "unix" ], - "semi": [ 2, "always" ], - "no-unused-vars": [ 2, { - "vars": "all", - "args": "none" - } ], - "spaced-comment": [ 2, "always" ] - }, - "env": { - "node": true, - "mocha": true - }, - "extends": "eslint:recommended" + "extends": ["plugin:prettier/recommended"], + "parser": "@babel/eslint-parser", + "parserOptions": { + "requireConfigFile": false + } } diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..7cd7018 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "trailingComma": "all", + "semi": false +} diff --git a/app.json b/app.json index 239c959..7aa29e4 100644 --- a/app.json +++ b/app.json @@ -1,24 +1,24 @@ { - "name": "Nuts", - "description": "Open source releases/downloads server with auto-updater", - "repository": "https://github.com/GitbookIO/nuts", - "website": "https://github.com/GitbookIO/nuts", - "keywords": ["releases", "download", "github", "squirrel", "desktop"], - "env": { - "GITHUB_REPO": { - "description": "Repository for the application's releases" - }, - "GITHUB_TOKEN": { - "description": "Access token for GitHub API" - }, - "API_USERNAME": { - "description": "Username for private API access" - }, - "API_PASSWORD": { - "description": "Password for private API access" - }, - "ROUTE_PREFIX": { - "description": "Custom prefix for all routes, defaults to /" - } + "name": "Nuts", + "description": "Open source releases/downloads server with auto-updater", + "repository": "https://github.com/GitbookIO/nuts", + "website": "https://github.com/GitbookIO/nuts", + "keywords": ["releases", "download", "github", "squirrel", "desktop"], + "env": { + "GITHUB_REPO": { + "description": "Repository for the application's releases" + }, + "GITHUB_TOKEN": { + "description": "Access token for GitHub API" + }, + "API_USERNAME": { + "description": "Username for private API access" + }, + "API_PASSWORD": { + "description": "Password for private API access" + }, + "ROUTE_PREFIX": { + "description": "Custom prefix for all routes, defaults to /" } + } } diff --git a/bin/web.js b/bin/web.js index 38e54bf..ce6d027 100644 --- a/bin/web.js +++ b/bin/web.js @@ -1,134 +1,155 @@ -var express = require('express'); -var uuid = require('uuid'); -var basicAuth = require('basic-auth'); -var Analytics = require('analytics-node'); -var nuts = require('../'); +var express = require("express") +var uuid = require("uuid") +var basicAuth = require("basic-auth") +var Analytics = require("analytics-node") +var nuts = require("../") -var app = express(); +var app = express() -var apiAuth = { - username: process.env.API_USERNAME, - password: process.env.API_PASSWORD -}; +var apiAuth = { + username: process.env.API_USERNAME, + password: process.env.API_PASSWORD, +} -var analytics = undefined; -var downloadEvent = process.env.ANALYTICS_EVENT_DOWNLOAD || 'download'; +var analytics = undefined +var downloadEvent = process.env.ANALYTICS_EVENT_DOWNLOAD || "download" if (process.env.ANALYTICS_TOKEN) { - analytics = new Analytics(process.env.ANALYTICS_TOKEN); + analytics = new Analytics(process.env.ANALYTICS_TOKEN) } var myNuts = nuts.Nuts({ - routePrefix: process.env.ROUTE_PREFIX, - repository: process.env.GITHUB_REPO, - token: process.env.GITHUB_TOKEN, - endpoint: process.env.GITHUB_ENDPOINT, - username: process.env.GITHUB_USERNAME, - password: process.env.GITHUB_PASSWORD, - timeout: process.env.VERSIONS_TIMEOUT, - cache: process.env.VERSIONS_CACHE, - refreshSecret: process.env.GITHUB_SECRET, - proxyAssets: !Boolean(process.env.DONT_PROXY_ASSETS) -}); + routePrefix: process.env.ROUTE_PREFIX, + repository: process.env.GITHUB_REPO, + token: process.env.GITHUB_TOKEN, + endpoint: process.env.GITHUB_ENDPOINT, + username: process.env.GITHUB_USERNAME, + password: process.env.GITHUB_PASSWORD, + timeout: process.env.VERSIONS_TIMEOUT, + cache: process.env.VERSIONS_CACHE, + refreshSecret: process.env.GITHUB_SECRET, + proxyAssets: !Boolean(process.env.DONT_PROXY_ASSETS), +}) // Control access to API -myNuts.before('api', function(access, next) { - if (!apiAuth.username) return next(); +myNuts.before("api", function (access, next) { + if (!apiAuth.username) return next() - function unauthorized() { - next(new Error('Invalid username/password for API')); - }; + function unauthorized() { + next(new Error("Invalid username/password for API")) + } - var user = basicAuth(access.req); - if (!user || !user.name || !user.pass) { - return unauthorized(); - }; + var user = basicAuth(access.req) + if (!user || !user.name || !user.pass) { + return unauthorized() + } - if (user.name === apiAuth.username && user.pass === apiAuth.password) { - return next(); - } else { - return unauthorized(); - }; -}); + if (user.name === apiAuth.username && user.pass === apiAuth.password) { + return next() + } else { + return unauthorized() + } +}) // Log download -myNuts.before('download', function(download, next) { - console.log('download', download.platform.filename, "for version", download.version.tag, "on channel", download.version.channel, "for", download.platform.type); - - next(); -}); -myNuts.after('download', function(download, next) { - console.log('downloaded', download.platform.filename, "for version", download.version.tag, "on channel", download.version.channel, "for", download.platform.type); - - // Track on segment if enabled - if (analytics) { - var userId = download.req.query.user; - - analytics.track({ - event: downloadEvent, - anonymousId: userId? null : uuid.v4(), - userId: userId, - properties: { - version: download.version.tag, - channel: download.version.channel, - platform: download.platform.type, - os: nuts.platforms.toType(download.platform.type) - } - }); - } - - next(); -}); +myNuts.before("download", function (download, next) { + console.log( + "download", + download.platform.filename, + "for version", + download.version.tag, + "on channel", + download.version.channel, + "for", + download.platform.type, + ) + + next() +}) +myNuts.after("download", function (download, next) { + console.log( + "downloaded", + download.platform.filename, + "for version", + download.version.tag, + "on channel", + download.version.channel, + "for", + download.platform.type, + ) + + // Track on segment if enabled + if (analytics) { + var userId = download.req.query.user + + analytics.track({ + event: downloadEvent, + anonymousId: userId ? null : uuid.v4(), + userId: userId, + properties: { + version: download.version.tag, + channel: download.version.channel, + platform: download.platform.type, + os: nuts.platforms.toType(download.platform.type), + }, + }) + } + + next() +}) if (process.env.TRUST_PROXY) { - try { - var trustProxyObject = JSON.parse(process.env.TRUST_PROXY); - app.set('trust proxy', trustProxyObject); - } - catch (e) { - app.set('trust proxy', process.env.TRUST_PROXY); - } + try { + var trustProxyObject = JSON.parse(process.env.TRUST_PROXY) + app.set("trust proxy", trustProxyObject) + } catch (e) { + app.set("trust proxy", process.env.TRUST_PROXY) + } } -app.use(myNuts.router); +app.use(myNuts.router) // Error handling -app.use(function(req, res, next) { - res.status(404).send("Page not found"); -}); -app.use(function(err, req, res, next) { - var msg = err.message || err; - var code = 500; - - console.error(err.stack || err); - - // Return error - res.format({ - 'text/plain': function(){ - res.status(code).send(msg); - }, - 'text/html': function () { - res.status(code).send(msg); - }, - 'application/json': function (){ - res.status(code).send({ - 'error': msg, - 'code': code - }); - } - }); -}); - -myNuts.init() - -// Start the HTTP server -.then(function() { - var server = app.listen(process.env.PORT || 5000, function () { - var host = server.address().address; - var port = server.address().port; - - console.log('Listening at http://%s:%s', host, port); - }); -}, function(err) { - console.log(err.stack || err); - process.exit(1); -}); +app.use(function (req, res, next) { + res.status(404).send("Page not found") +}) +app.use(function (err, req, res, next) { + var msg = err.message || err + var code = 500 + + console.error(err.stack || err) + + // Return error + res.format({ + "text/plain": function () { + res.status(code).send(msg) + }, + "text/html": function () { + res.status(code).send(msg) + }, + "application/json": function () { + res.status(code).send({ + error: msg, + code: code, + }) + }, + }) +}) + +myNuts + .init() + + // Start the HTTP server + .then( + function () { + var server = app.listen(process.env.PORT || 5000, function () { + var host = server.address().address + var port = server.address().port + + console.log("Listening at http://%s:%s", host, port) + }) + }, + function (err) { + console.log(err.stack || err) + process.exit(1) + }, + ) diff --git a/book.js b/book.js index 32d867b..2b5bf22 100644 --- a/book.js +++ b/book.js @@ -1,24 +1,24 @@ -var pkg = require('./package.json'); +var pkg = require("./package.json") module.exports = { - // Documentation for Nuts is stored under "docs" - root: './docs', - title: 'Nuts Documentation', + // Documentation for Nuts is stored under "docs" + root: "./docs", + title: "Nuts Documentation", - // Enforce use of GitBook v3 - gitbook: '>=3.0.0-pre.0', + // Enforce use of GitBook v3 + gitbook: ">=3.0.0-pre.0", - // Use the "official" theme - plugins: ['theme-official', 'sitemap'], - theme: 'official', + // Use the "official" theme + plugins: ["theme-official", "sitemap"], + theme: "official", - variables: { - version: pkg.version - }, + variables: { + version: pkg.version, + }, - pluginsConfig: { - sitemap: { - hostname: 'https://nuts.gitbook.com' - } - } -}; + pluginsConfig: { + sitemap: { + hostname: "https://nuts.gitbook.com", + }, + }, +} diff --git a/lib/api.js b/lib/api.js index 70e789f..0c4cb6d 100644 --- a/lib/api.js +++ b/lib/api.js @@ -1,44 +1,40 @@ -var startTime = Date.now(); -var Q = require('q'); +var startTime = Date.now() +var Q = require("q") module.exports = { - 'status': function () { - return { - uptime: (Date.now() - startTime)/1000 - }; - }, - - 'versions': function (req) { - return this.versions.filter({ - platform: req.query.platform, - channel: req.query.channel || '*' - }); - }, + status: function () { + return { + uptime: (Date.now() - startTime) / 1000, + } + }, - 'channels': function () { - return this.versions.channels(); - }, + versions: function (req) { + return this.versions.filter({ + platform: req.query.platform, + channel: req.query.channel || "*", + }) + }, - 'refresh': function () { - return Q() - .then(this.backend.onRelease) - .thenResolve({done: true} - ); - }, + channels: function () { + return this.versions.channels() + }, - 'version/:tag': function (req) { - return this.versions.resolve({ - tag: req.params.tag, - channel: '*' - }); - }, + refresh: function () { + return Q().then(this.backend.onRelease).thenResolve({ done: true }) + }, - 'resolve': function(req) { - return this.versions.resolve({ - channel: req.query.channel, - platform: req.query.platform, - tag: req.query.tag - }); - } -}; + "version/:tag": function (req) { + return this.versions.resolve({ + tag: req.params.tag, + channel: "*", + }) + }, + resolve: function (req) { + return this.versions.resolve({ + channel: req.query.channel, + platform: req.query.platform, + tag: req.query.tag, + }) + }, +} diff --git a/lib/backends/backend.js b/lib/backends/backend.js index eec766a..caf8b7b 100644 --- a/lib/backends/backend.js +++ b/lib/backends/backend.js @@ -1,122 +1,115 @@ -var _ = require('lodash'); -var Q = require('q'); -var path = require('path'); -var os = require('os'); -var destroy = require('destroy'); -var LRU = require('lru-diskcache'); -var streamRes = require('stream-res'); -var Buffer = require('buffer').Buffer; +var _ = require("lodash") +var Q = require("q") +var path = require("path") +var os = require("os") +var destroy = require("destroy") +var LRU = require("lru-diskcache") +var streamRes = require("stream-res") +var Buffer = require("buffer").Buffer function Backend(nuts, opts) { - this.cacheId = 0; - this.nuts = nuts; - this.opts = _.defaults(opts || {}, { - // Folder to cache assets - cache: path.resolve(os.tmpdir(), 'nuts'), - - // Cache configuration - cacheMax: 500 * 1024 * 1024, - cacheMaxAge: 60 * 60 * 1000, - }); - - // Create cache - this.cache = LRU(opts.cache, { - max: opts.cacheMax, - maxAge: opts.cacheMaxAge - }); - - _.bindAll(this); + this.cacheId = 0 + this.nuts = nuts + this.opts = _.defaults(opts || {}, { + // Folder to cache assets + cache: path.resolve(os.tmpdir(), "nuts"), + + // Cache configuration + cacheMax: 500 * 1024 * 1024, + cacheMaxAge: 60 * 60 * 1000, + }) + + // Create cache + this.cache = LRU(opts.cache, { + max: opts.cacheMax, + maxAge: opts.cacheMaxAge, + }) + + _.bindAll(this) } // Memoize a function -Backend.prototype.memoize = function(fn) { - var that = this; +Backend.prototype.memoize = function (fn) { + var that = this - return _.memoize(fn, function() { - return that.cacheId+Math.ceil(Date.now()/that.opts.cacheMaxAge) - }); -}; + return _.memoize(fn, function () { + return that.cacheId + Math.ceil(Date.now() / that.opts.cacheMaxAge) + }) +} // New release? clear cache -Backend.prototype.onRelease = function() { - this.cacheId++; -}; +Backend.prototype.onRelease = function () { + this.cacheId++ +} // Initialize the backend -Backend.prototype.init = function() { - this.cache.init(); - return Q(); -}; +Backend.prototype.init = function () { + this.cache.init() + return Q() +} // List all releases for this repository -Backend.prototype.releases = function() { - -}; +Backend.prototype.releases = function () {} // Return stream for an asset -Backend.prototype.serveAsset = function(asset, req, res) { - var that = this; - var cacheKey = asset.id; - - function outputStream(stream) { - var d = Q.defer(); - streamRes(res, stream, d.makeNodeResolver()); - return d.promise; - } - - res.header('Content-Length', asset.size); - res.attachment(asset.filename); - - // Key exists - if (that.cache.has(cacheKey)) { - return that.cache.getStream(cacheKey) - .then(outputStream); - } - - return that.getAssetStream(asset) - .then(function(stream) { - return Q.all([ - // Cache the stream - that.cache.set(cacheKey, stream), +Backend.prototype.serveAsset = function (asset, req, res) { + var that = this + var cacheKey = asset.id + + function outputStream(stream) { + var d = Q.defer() + streamRes(res, stream, d.makeNodeResolver()) + return d.promise + } + + res.header("Content-Length", asset.size) + res.attachment(asset.filename) + + // Key exists + if (that.cache.has(cacheKey)) { + return that.cache.getStream(cacheKey).then(outputStream) + } + + return that.getAssetStream(asset).then(function (stream) { + return Q.all([ + // Cache the stream + that.cache.set(cacheKey, stream), + + // Send the stream to the user + outputStream(stream), + ]) + }) +} - // Send the stream to the user - outputStream(stream) - ]); - }); -}; +// Return stream for an asset +Backend.prototype.getAssetStream = function (asset) {} // Return stream for an asset -Backend.prototype.getAssetStream = function(asset) { +Backend.prototype.readAsset = function (asset) { + return this.getAssetStream(asset).then(function (res) { + var d = Q.defer() + var output = Buffer([]) + + function cleanup() { + destroy(res) + res.removeAllListeners() + } -}; + res + .on("data", function (buf) { + output = Buffer.concat([output, buf]) + }) + .on("error", function (err) { + cleanup() + d.reject(err) + }) + .on("end", function () { + cleanup() + d.resolve(output) + }) + + return d.promise + }) +} -// Return stream for an asset -Backend.prototype.readAsset = function(asset) { - return this.getAssetStream(asset) - .then(function(res) { - var d = Q.defer(); - var output = Buffer([]); - - function cleanup() { - destroy(res); - res.removeAllListeners(); - } - - res.on('data', function(buf) { - output = Buffer.concat([output, buf]); - }) - .on('error', function(err) { - cleanup(); - d.reject(err); - }) - .on('end', function() { - cleanup(); - d.resolve(output); - }); - - return d.promise - - }) -}; - -module.exports = Backend; +module.exports = Backend diff --git a/lib/backends/github.js b/lib/backends/github.js index 3eaf155..ba2bbcc 100644 --- a/lib/backends/github.js +++ b/lib/backends/github.js @@ -1,92 +1,92 @@ -var _ = require('lodash'); -var Q = require('q'); -var util = require('util'); -var destroy = require('destroy'); -var GitHub = require('octocat'); -var request = require('request'); -var Buffer = require('buffer').Buffer; -var githubWebhook = require('github-webhook-handler'); +var _ = require("lodash") +var Q = require("q") +var util = require("util") +var destroy = require("destroy") +var GitHub = require("octocat") +var request = require("request") +var Buffer = require("buffer").Buffer +var githubWebhook = require("github-webhook-handler") -var Backend = require('./backend'); +var Backend = require("./backend") function GitHubBackend() { - var that = this; - Backend.apply(this, arguments); + var that = this + Backend.apply(this, arguments) - this.opts = _.defaults(this.opts || {}, { - proxyAssets: true - }); + this.opts = _.defaults(this.opts || {}, { + proxyAssets: true, + }) - if ((!this.opts.username || !this.opts.password) && (!this.opts.token)) { - throw new Error('GitHub backend require "username" and "token" options'); - } + if ((!this.opts.username || !this.opts.password) && !this.opts.token) { + throw new Error('GitHub backend require "username" and "token" options') + } - this.client = new GitHub({ - token: this.opts.token, - endpoint: this.opts.endpoint, - username: this.opts.username, - password: this.opts.password - }); + this.client = new GitHub({ + token: this.opts.token, + endpoint: this.opts.endpoint, + username: this.opts.username, + password: this.opts.password, + }) - this.ghrepo = this.client.repo(this.opts.repository); - this.releases = this.memoize(this._releases); + this.ghrepo = this.client.repo(this.opts.repository) + this.releases = this.memoize(this._releases) - // GitHub webhook to refresh list of versions - this.webhookHandler = githubWebhook({ - path: '/refresh', - secret: this.opts.refreshSecret - }); + // GitHub webhook to refresh list of versions + this.webhookHandler = githubWebhook({ + path: "/refresh", + secret: this.opts.refreshSecret, + }) - // Webhook from GitHub - this.webhookHandler.on('release', function(event) { - that.onRelease(); - }); - this.nuts.router.use(this.webhookHandler); + // Webhook from GitHub + this.webhookHandler.on("release", function (event) { + that.onRelease() + }) + this.nuts.router.use(this.webhookHandler) } -util.inherits(GitHubBackend, Backend); +util.inherits(GitHubBackend, Backend) // List all releases for this repository -GitHubBackend.prototype._releases = function() { - return this.ghrepo.releases() - .then(function(page) { - return page.all(); - }); -}; +GitHubBackend.prototype._releases = function () { + return this.ghrepo.releases().then(function (page) { + return page.all() + }) +} // Return stream for an asset -GitHubBackend.prototype.serveAsset = function(asset, req, res) { - if (!this.opts.proxyAssets) { - res.redirect(asset.raw.browser_download_url); - } else { - return Backend.prototype.serveAsset.apply(this, arguments); - } -}; +GitHubBackend.prototype.serveAsset = function (asset, req, res) { + if (!this.opts.proxyAssets) { + res.redirect(asset.raw.browser_download_url) + } else { + return Backend.prototype.serveAsset.apply(this, arguments) + } +} // Return stream for an asset -GitHubBackend.prototype.getAssetStream = function(asset) { - var headers = { - 'User-Agent': 'nuts', - 'Accept': 'application/octet-stream' - }; - var httpAuth; +GitHubBackend.prototype.getAssetStream = function (asset) { + var headers = { + "User-Agent": "nuts", + Accept: "application/octet-stream", + } + var httpAuth - if (this.opts.token) { - headers['Authorization'] = 'token '+this.opts.token; - } else if (this.opts.username) { - httpAuth = { - user: this.opts.username, - pass: this.opts.password, - sendImmediately: true - }; + if (this.opts.token) { + headers["Authorization"] = "token " + this.opts.token + } else if (this.opts.username) { + httpAuth = { + user: this.opts.username, + pass: this.opts.password, + sendImmediately: true, } + } - return Q(request({ - uri: asset.raw.url, - method: 'get', - headers: headers, - auth: httpAuth - })); -}; - + return Q( + request({ + uri: asset.raw.url, + method: "get", + headers: headers, + auth: httpAuth, + }), + ) +} -module.exports = GitHubBackend; +module.exports = GitHubBackend diff --git a/lib/backends/index.js b/lib/backends/index.js index 193703c..c3065d9 100644 --- a/lib/backends/index.js +++ b/lib/backends/index.js @@ -1,10 +1,10 @@ -var _ = require('lodash'); +var _ = require("lodash") var BACKENDS = { - github: require('./github') -}; + github: require("./github"), +} -module.exports = function(backend) { - if (_.isString(backend)) return BACKENDS[backend]; - return backend; -}; +module.exports = function (backend) { + if (_.isString(backend)) return BACKENDS[backend] + return backend +} diff --git a/lib/index.js b/lib/index.js index bbe28f1..79dce48 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,9 +1,9 @@ -var Nuts = require('./nuts'); -var platforms = require('./utils/platforms'); -var winReleases = require('./utils/win-releases'); +var Nuts = require("./nuts") +var platforms = require("./utils/platforms") +var winReleases = require("./utils/win-releases") module.exports = { - Nuts: Nuts, - platforms: platforms, - winReleases: winReleases -}; + Nuts: Nuts, + platforms: platforms, + winReleases: winReleases, +} diff --git a/lib/nuts.js b/lib/nuts.js index 9219d79..1f0e325 100644 --- a/lib/nuts.js +++ b/lib/nuts.js @@ -1,403 +1,467 @@ -var _ = require('lodash'); -var Q = require('q'); -var Feed = require('feed'); -var urljoin = require('urljoin.js'); -var Understudy = require('understudy'); -var express = require('express'); -var useragent = require('express-useragent'); - -var BACKENDS = require('./backends'); -var Versions = require('./versions'); -var notes = require('./utils/notes'); -var platforms = require('./utils/platforms'); -var winReleases = require('./utils/win-releases'); -var API_METHODS = require('./api'); +var _ = require("lodash") +var Q = require("q") +var Feed = require("feed") +var urljoin = require("urljoin.js") +var Understudy = require("understudy") +var express = require("express") +var useragent = require("express-useragent") + +var BACKENDS = require("./backends") +var Versions = require("./versions") +var notes = require("./utils/notes") +var platforms = require("./utils/platforms") +var winReleases = require("./utils/win-releases") +var API_METHODS = require("./api") function getFullUrl(req) { - return req.protocol + '://' + req.get('host') + req.originalUrl; + return req.protocol + "://" + req.get("host") + req.originalUrl } function Nuts(opts) { - if (!(this instanceof Nuts)) return new Nuts(opts); - var that = this; - - Understudy.call(this); - _.bindAll(this); - - this.opts = _.defaults(opts || {}, { - // Backend to use - backend: 'github', - - // Timeout for releases cache (seconds) - timeout: 60*60*1000, - - // Pre-fetch list of releases at startup - preFetch: true, - - // Secret for GitHub webhook - refreshSecret: 'secret', - - // Prefix for all routes - routePrefix: '/' - }); - - if (this.opts.routePrefix.substr(this.opts.routePrefix.length - 1, 1) !== '/') { - throw new Error('ROUTE_PREIX must end with a slash'); - } - - // .init() is now a memoized version of ._init() - this.init = _.memoize(this._init); - - // Create router - this.router = express.Router(); - - // Create backend - this.backend = new (BACKENDS(this.opts.backend))(this, this.opts); - this.versions = new Versions(this.backend); - - // Bind routes - this.router.use(useragent.express()); - - this.router.get(`${that.opts.routePrefix}`, this.onDownload); - this.router.get(`${that.opts.routePrefix}download/channel/:channel/:platform?`, this.onDownload); - this.router.get(`${that.opts.routePrefix}download/version/:tag/:platform?`, this.onDownload); - this.router.get(`${that.opts.routePrefix}download/:tag/:filename`, this.onDownload); - this.router.get(`${that.opts.routePrefix}download/:platform?`, this.onDownload); - - this.router.get(`${that.opts.routePrefix}feed/channel/:channel.atom`, this.onServeVersionsFeed); - - this.router.get(`${that.opts.routePrefix}update`, this.onUpdateRedirect); - this.router.get(`${that.opts.routePrefix}update/:platform/:version`, this.onUpdate); - this.router.get(`${that.opts.routePrefix}update/channel/:channel/:platform/:version`, this.onUpdate); - this.router.get(`${that.opts.routePrefix}update/:platform/:version/RELEASES`, this.onUpdateWin); - this.router.get(`${that.opts.routePrefix}update/channel/:channel/:platform/:version/RELEASES`, this.onUpdateWin); - - this.router.get(`${that.opts.routePrefix}notes/:version?`, this.onServeNotes); - - // Bind API - this.router.use(`${that.opts.routePrefix}api`, this.onAPIAccessControl); - _.each(API_METHODS, function(method, route) { - this.router.get(`${that.opts.routePrefix}api/${route}`, function(req, res, next) { - return Q() - .then(function() { - return method.call(that, req); + if (!(this instanceof Nuts)) return new Nuts(opts) + var that = this + + Understudy.call(this) + _.bindAll(this) + + this.opts = _.defaults(opts || {}, { + // Backend to use + backend: "github", + + // Timeout for releases cache (seconds) + timeout: 60 * 60 * 1000, + + // Pre-fetch list of releases at startup + preFetch: true, + + // Secret for GitHub webhook + refreshSecret: "secret", + + // Prefix for all routes + routePrefix: "/", + }) + + if ( + this.opts.routePrefix.substr(this.opts.routePrefix.length - 1, 1) !== "/" + ) { + throw new Error("ROUTE_PREIX must end with a slash") + } + + // .init() is now a memoized version of ._init() + this.init = _.memoize(this._init) + + // Create router + this.router = express.Router() + + // Create backend + this.backend = new (BACKENDS(this.opts.backend))(this, this.opts) + this.versions = new Versions(this.backend) + + // Bind routes + this.router.use(useragent.express()) + + this.router.get(`${that.opts.routePrefix}`, this.onDownload) + this.router.get( + `${that.opts.routePrefix}download/channel/:channel/:platform?`, + this.onDownload, + ) + this.router.get( + `${that.opts.routePrefix}download/version/:tag/:platform?`, + this.onDownload, + ) + this.router.get( + `${that.opts.routePrefix}download/:tag/:filename`, + this.onDownload, + ) + this.router.get( + `${that.opts.routePrefix}download/:platform?`, + this.onDownload, + ) + + this.router.get( + `${that.opts.routePrefix}feed/channel/:channel.atom`, + this.onServeVersionsFeed, + ) + + this.router.get(`${that.opts.routePrefix}update`, this.onUpdateRedirect) + this.router.get( + `${that.opts.routePrefix}update/:platform/:version`, + this.onUpdate, + ) + this.router.get( + `${that.opts.routePrefix}update/channel/:channel/:platform/:version`, + this.onUpdate, + ) + this.router.get( + `${that.opts.routePrefix}update/:platform/:version/RELEASES`, + this.onUpdateWin, + ) + this.router.get( + `${that.opts.routePrefix}update/channel/:channel/:platform/:version/RELEASES`, + this.onUpdateWin, + ) + + this.router.get(`${that.opts.routePrefix}notes/:version?`, this.onServeNotes) + + // Bind API + this.router.use(`${that.opts.routePrefix}api`, this.onAPIAccessControl) + _.each( + API_METHODS, + function (method, route) { + this.router.get( + `${that.opts.routePrefix}api/${route}`, + function (req, res, next) { + return Q() + .then(function () { + return method.call(that, req) }) - .then(function(result) { - res.send(result); - }, next); - }); - }, this); + .then(function (result) { + res.send(result) + }, next) + }, + ) + }, + this, + ) } // _init does the real init work, initializing backend and prefetching versions -Nuts.prototype._init = function() { - var that = this; - return Q() - .then(function() { - return that.backend.init(); +Nuts.prototype._init = function () { + var that = this + return Q() + .then(function () { + return that.backend.init() + }) + .then(function () { + if (!that.opts.preFetch) return + return that.versions.list() }) - .then(function() { - if (!that.opts.preFetch) return - return that.versions.list(); - }); } - // Perform a hook using promised functions -Nuts.prototype.performQ = function(name, arg, fn) { - var that = this; - fn = fn || function() { }; - - return Q.nfcall(this.perform, name, arg, function (next) { - Q() - .then(function() { - return fn.call(that, arg); - }) - .then(function() { - next(); - }, next); - }) -}; +Nuts.prototype.performQ = function (name, arg, fn) { + var that = this + fn = fn || function () {} + + return Q.nfcall(this.perform, name, arg, function (next) { + Q() + .then(function () { + return fn.call(that, arg) + }) + .then(function () { + next() + }, next) + }) +} // Serve an asset to the response -Nuts.prototype.serveAsset = function(req, res, version, asset) { - var that = this; - - return that.init() - .then(function() { - return that.performQ('download', { - req: req, - version: version, - platform: asset - }, function() { - return that.backend.serveAsset(asset, req, res) - }); - }); -}; +Nuts.prototype.serveAsset = function (req, res, version, asset) { + var that = this + + return that.init().then(function () { + return that.performQ( + "download", + { + req: req, + version: version, + platform: asset, + }, + function () { + return that.backend.serveAsset(asset, req, res) + }, + ) + }) +} // Handler for download routes -Nuts.prototype.onDownload = function(req, res, next) { - var that = this; - var channel = req.params.channel; - var platform = req.params.platform; - var tag = req.params.tag || 'latest'; - var filename = req.params.filename; - var filetypeWanted = req.query.filetype; - - // When serving a specific file, platform is not required - if (!filename) { - // Detect platform from useragent - if (!platform) { - if (req.useragent.isMac) platform = platforms.OSX; - if (req.useragent.isWindows) platform = platforms.WINDOWS; - if (req.useragent.isLinux) platform = platforms.LINUX; - if (req.useragent.isLinux64) platform = platforms.LINUX_64; - } - - if (!platform) return next(new Error('No platform specified and impossible to detect one')); - } else { - platform = null; +Nuts.prototype.onDownload = function (req, res, next) { + var that = this + var channel = req.params.channel + var platform = req.params.platform + var tag = req.params.tag || "latest" + var filename = req.params.filename + var filetypeWanted = req.query.filetype + + // When serving a specific file, platform is not required + if (!filename) { + // Detect platform from useragent + if (!platform) { + if (req.useragent.isMac) platform = platforms.OSX + if (req.useragent.isWindows) platform = platforms.WINDOWS + if (req.useragent.isLinux) platform = platforms.LINUX + if (req.useragent.isLinux64) platform = platforms.LINUX_64 } - // If specific version, don't enforce a channel - if (tag != 'latest') channel = '*'; - - this.versions.resolve({ - channel: channel, - platform: platform, - tag: tag + if (!platform) + return next( + new Error("No platform specified and impossible to detect one"), + ) + } else { + platform = null + } + + // If specific version, don't enforce a channel + if (tag != "latest") channel = "*" + + this.versions + .resolve({ + channel: channel, + platform: platform, + tag: tag, }) // Fallback to any channels if no version found on stable one - .fail(function(err) { - if (channel || tag != 'latest') throw err; - - return that.versions.resolve({ - channel: '*', - platform: platform, - tag: tag - }); + .fail(function (err) { + if (channel || tag != "latest") throw err + + return that.versions.resolve({ + channel: "*", + platform: platform, + tag: tag, + }) }) // Serve downloads - .then(function(version) { - var asset; - - if (filename) { - asset = _.find(version.platforms, { - filename: filename - }); - } else { - asset = platforms.resolve(version, platform, { - wanted: filetypeWanted? '.'+filetypeWanted : null - }); - } - - if (!asset) throw new Error("No download available for platform "+platform+" for version "+version.tag+" ("+(channel || "beta")+")"); - - // Call analytic middleware, then serve - return that.serveAsset(req, res, version, asset); - }) - .fail(next); -}; + .then(function (version) { + var asset + if (filename) { + asset = _.find(version.platforms, { + filename: filename, + }) + } else { + asset = platforms.resolve(version, platform, { + wanted: filetypeWanted ? "." + filetypeWanted : null, + }) + } + + if (!asset) + throw new Error( + "No download available for platform " + + platform + + " for version " + + version.tag + + " (" + + (channel || "beta") + + ")", + ) + + // Call analytic middleware, then serve + return that.serveAsset(req, res, version, asset) + }) + .fail(next) +} // Request to update -Nuts.prototype.onUpdateRedirect = function(req, res, next) { - var that = this; +Nuts.prototype.onUpdateRedirect = function (req, res, next) { + var that = this - Q() - .then(function() { - if (!req.query.version) throw new Error('Requires "version" parameter'); - if (!req.query.platform) throw new Error('Requires "platform" parameter'); + Q() + .then(function () { + if (!req.query.version) throw new Error('Requires "version" parameter') + if (!req.query.platform) throw new Error('Requires "platform" parameter') - return res.redirect(`${that.opts.routePrefix}update/${req.query.platform}/${req.query.version}`); + return res.redirect( + `${that.opts.routePrefix}update/${req.query.platform}/${req.query.version}`, + ) }) - .fail(next); -}; + .fail(next) +} // Updater used by OSX (Squirrel.Mac) and others -Nuts.prototype.onUpdate = function(req, res, next) { - var that = this; - var fullUrl = getFullUrl(req); - var platform = req.params.platform; - var channel = req.params.channel || '*'; - var tag = req.params.version; - var filetype = req.query.filetype ? req.query.filetype : "zip"; - - Q() - .then(function() { - if (!tag) throw new Error('Requires "version" parameter'); - if (!platform) throw new Error('Requires "platform" parameter'); - - platform = platforms.detect(platform); - - return that.versions.filter({ - tag: '>='+tag, - platform: platform, - channel: channel - }); +Nuts.prototype.onUpdate = function (req, res, next) { + var that = this + var fullUrl = getFullUrl(req) + var platform = req.params.platform + var channel = req.params.channel || "*" + var tag = req.params.version + var filetype = req.query.filetype ? req.query.filetype : "zip" + + Q() + .then(function () { + if (!tag) throw new Error('Requires "version" parameter') + if (!platform) throw new Error('Requires "platform" parameter') + + platform = platforms.detect(platform) + + return that.versions.filter({ + tag: ">=" + tag, + platform: platform, + channel: channel, + }) }) - .then(function(versions) { - var latest = _.first(versions); - if (!latest || latest.tag == tag) return res.status(204).send('No updates'); - - var notesSlice = versions.slice(0, -1); - if (versions.length === 1) { - notesSlice = [versions[0]]; - } - var releaseNotes = notes.merge(notesSlice, { includeTag: false }); - console.error(latest.tag); - var gitFilePath = (channel === '*' ? '/../../../' : '/../../../../../'); - res.status(200).send({ - "url": urljoin(fullUrl, gitFilePath, `download/version/${latest.tag}/${platform}?filetype=${filetype}`), - "name": latest.tag, - "notes": releaseNotes, - "pub_date": latest.published_at.toISOString() - }); + .then(function (versions) { + var latest = _.first(versions) + if (!latest || latest.tag == tag) + return res.status(204).send("No updates") + + var notesSlice = versions.slice(0, -1) + if (versions.length === 1) { + notesSlice = [versions[0]] + } + var releaseNotes = notes.merge(notesSlice, { includeTag: false }) + console.error(latest.tag) + var gitFilePath = channel === "*" ? "/../../../" : "/../../../../../" + res.status(200).send({ + url: urljoin( + fullUrl, + gitFilePath, + `download/version/${latest.tag}/${platform}?filetype=${filetype}`, + ), + name: latest.tag, + notes: releaseNotes, + pub_date: latest.published_at.toISOString(), + }) }) - .fail(next); -}; + .fail(next) +} // Update Windows (Squirrel.Windows) // Auto-updates: Squirrel.Windows: serve RELEASES from latest version // Currently, it will only serve a full.nupkg of the latest release with a normalized filename (for pre-release) -Nuts.prototype.onUpdateWin = function(req, res, next) { - var that = this; - - var fullUrl = getFullUrl(req); - var platform = 'win_32'; - var channel = req.params.channel || '*'; - var tag = req.params.version; - - that.init() - .then(function() { - platform = platforms.detect(platform); - - return that.versions.filter({ - tag: '>='+tag, - platform: platform, - channel: channel - }); - }) - .then(function(versions) { - // Update needed? - var latest = _.first(versions); - if (!latest) throw new Error("Version not found"); - - // File exists - var asset = _.find(latest.platforms, { - filename: 'RELEASES' - }); - if (!asset) throw new Error("File not found"); - - return that.backend.readAsset(asset) - .then(function(content) { - var releases = winReleases.parse(content.toString('utf-8')); - - releases = _.chain(releases) - - // Change filename to use download proxy - .map(function(entry) { - var gitFilePath = (channel === '*' ? '../../../../' : '../../../../../../'); - entry.filename = urljoin(fullUrl, gitFilePath, `download/${entry.semver}/${entry.filename}`); - - return entry; - }) +Nuts.prototype.onUpdateWin = function (req, res, next) { + var that = this - .value(); + var fullUrl = getFullUrl(req) + var platform = "win_32" + var channel = req.params.channel || "*" + var tag = req.params.version - var output = winReleases.generate(releases); + that + .init() + .then(function () { + platform = platforms.detect(platform) - res.header('Content-Length', output.length); - res.attachment("RELEASES"); - res.send(output); - }); + return that.versions.filter({ + tag: ">=" + tag, + platform: platform, + channel: channel, + }) }) - .fail(next); -}; + .then(function (versions) { + // Update needed? + var latest = _.first(versions) + if (!latest) throw new Error("Version not found") + + // File exists + var asset = _.find(latest.platforms, { + filename: "RELEASES", + }) + if (!asset) throw new Error("File not found") + + return that.backend.readAsset(asset).then(function (content) { + var releases = winReleases.parse(content.toString("utf-8")) + + releases = _.chain(releases) + + // Change filename to use download proxy + .map(function (entry) { + var gitFilePath = + channel === "*" ? "../../../../" : "../../../../../../" + entry.filename = urljoin( + fullUrl, + gitFilePath, + `download/${entry.semver}/${entry.filename}`, + ) + + return entry + }) + + .value() + + var output = winReleases.generate(releases) + + res.header("Content-Length", output.length) + res.attachment("RELEASES") + res.send(output) + }) + }) + .fail(next) +} // Serve releases notes -Nuts.prototype.onServeNotes = function(req, res, next) { - var that = this; - var tag = req.params.version; - - Q() - .then(function() { - return that.versions.filter({ - tag: tag? '>='+tag : '*', - channel: '*' - }); +Nuts.prototype.onServeNotes = function (req, res, next) { + var that = this + var tag = req.params.version + + Q() + .then(function () { + return that.versions.filter({ + tag: tag ? ">=" + tag : "*", + channel: "*", + }) }) - .then(function(versions) { - var latest = _.first(versions); - - if (!latest) throw new Error('No versions matching'); - - res.format({ - 'text/plain': function(){ - res.send(notes.merge(versions)); - }, - 'application/json': function(){ - res.send({ - "notes": notes.merge(versions, { includeTag: false }), - "pub_date": latest.published_at.toISOString() - }); - }, - 'default': function() { - res.send(releaseNotes); - } - }); + .then(function (versions) { + var latest = _.first(versions) + + if (!latest) throw new Error("No versions matching") + + res.format({ + "text/plain": function () { + res.send(notes.merge(versions)) + }, + "application/json": function () { + res.send({ + notes: notes.merge(versions, { includeTag: false }), + pub_date: latest.published_at.toISOString(), + }) + }, + default: function () { + res.send(releaseNotes) + }, + }) }) - .fail(next); -}; + .fail(next) +} // Serve versions list as RSS -Nuts.prototype.onServeVersionsFeed = function(req, res, next) { - var that = this; - var channel = req.params.channel || 'all'; - var channelId = channel === 'all'? '*' : channel; - var fullUrl = getFullUrl(req); - - var feed = new Feed({ - id: 'versions/channels/'+channel, - title: 'Versions (' + channel + ')', - link: fullUrl - }); - - Q() - .then(function() { - return that.versions.filter({ - channel: channelId - }); - }) - .then(function(versions) { - _.each(versions, function(version) { - feed.addItem({ - title: version.tag, - link: urljoin(fullUrl, '/../../../', `download/version/${version.tag}`), - description: version.notes, - date: version.published_at, - author: [] - }); - }); - - res.set('Content-Type', 'application/atom+xml; charset=utf-8'); - res.send(feed.render('atom-1.0')); +Nuts.prototype.onServeVersionsFeed = function (req, res, next) { + var that = this + var channel = req.params.channel || "all" + var channelId = channel === "all" ? "*" : channel + var fullUrl = getFullUrl(req) + + var feed = new Feed({ + id: "versions/channels/" + channel, + title: "Versions (" + channel + ")", + link: fullUrl, + }) + + Q() + .then(function () { + return that.versions.filter({ + channel: channelId, + }) }) - .fail(next); -}; + .then(function (versions) { + _.each(versions, function (version) { + feed.addItem({ + title: version.tag, + link: urljoin( + fullUrl, + "/../../../", + `download/version/${version.tag}`, + ), + description: version.notes, + date: version.published_at, + author: [], + }) + }) -// Control access to the API -Nuts.prototype.onAPIAccessControl = function(req, res, next) { - this.performQ('api', { - req: req, - res: res + res.set("Content-Type", "application/atom+xml; charset=utf-8") + res.send(feed.render("atom-1.0")) }) - .then(function() { - next(); - }, next); -}; + .fail(next) +} +// Control access to the API +Nuts.prototype.onAPIAccessControl = function (req, res, next) { + this.performQ("api", { + req: req, + res: res, + }).then(function () { + next() + }, next) +} -module.exports = Nuts; +module.exports = Nuts diff --git a/lib/utils/notes.js b/lib/utils/notes.js index f916fdd..6a23e5a 100644 --- a/lib/utils/notes.js +++ b/lib/utils/notes.js @@ -1,33 +1,33 @@ -var _ = require('lodash'); +var _ = require("lodash") // Merge release notes for a list of versions function mergeForVersions(versions, opts) { - opts = _.defaults(opts || {}, { - includeTag: true - }); + opts = _.defaults(opts || {}, { + includeTag: true, + }) - return _.chain(versions) - .reduce(function(prev, version) { - if (!version.notes) return prev; + return _.chain(versions) + .reduce(function (prev, version) { + if (!version.notes) return prev - // Include tag as title - if (opts.includeTag) { - prev = prev + '## ' + version.tag + '\n'; - } + // Include tag as title + if (opts.includeTag) { + prev = prev + "## " + version.tag + "\n" + } - // Include notes - prev = prev + version.notes + '\n'; + // Include notes + prev = prev + version.notes + "\n" - // New lines - if (opts.includeTag) { - prev = prev + '\n'; - } + // New lines + if (opts.includeTag) { + prev = prev + "\n" + } - return prev; - }, '') - .value(); + return prev + }, "") + .value() } module.exports = { - merge: mergeForVersions -}; + merge: mergeForVersions, +} diff --git a/lib/utils/platforms.js b/lib/utils/platforms.js index 5ee5f79..90e44f2 100644 --- a/lib/utils/platforms.js +++ b/lib/utils/platforms.js @@ -1,136 +1,162 @@ -var _ = require('lodash'); -var path = require('path'); +var _ = require("lodash") +var path = require("path") var platforms = { - LINUX: 'linux', - LINUX_32: 'linux_32', - LINUX_64: 'linux_64', - LINUX_RPM: 'linux_rpm', - LINUX_RPM_32: 'linux_rpm_32', - LINUX_RPM_64: 'linux_rpm_64', - LINUX_DEB: 'linux_deb', - LINUX_DEB_32: 'linux_deb_32', - LINUX_DEB_64: 'linux_deb_64', - OSX: 'osx', - OSX_32: 'osx_32', - OSX_64: 'osx_64', - WINDOWS: 'windows', - WINDOWS_32: 'windows_32', - WINDOWS_64: 'windows_64', - - detect: detectPlatform -}; + LINUX: "linux", + LINUX_32: "linux_32", + LINUX_64: "linux_64", + LINUX_RPM: "linux_rpm", + LINUX_RPM_32: "linux_rpm_32", + LINUX_RPM_64: "linux_rpm_64", + LINUX_DEB: "linux_deb", + LINUX_DEB_32: "linux_deb_32", + LINUX_DEB_64: "linux_deb_64", + OSX: "osx", + OSX_32: "osx_32", + OSX_64: "osx_64", + WINDOWS: "windows", + WINDOWS_32: "windows_32", + WINDOWS_64: "windows_64", + + detect: detectPlatform, +} // Reduce a platfrom id to its type function platformToType(platform) { - return _.first(platform.split('_')); + return _.first(platform.split("_")) } // Detect and normalize the platform name function detectPlatform(platform) { - var name = platform.toLowerCase(); - var prefix = "", suffix = ""; - - // Detect NuGet/Squirrel.Windows files - if (name == 'releases' || hasSuffix(name, '.nupkg')) return platforms.WINDOWS_32; - - // Detect prefix: osx, widnows or linux - if (_.contains(name, 'win') - || hasSuffix(name, '.exe')) prefix = platforms.WINDOWS; - - if (_.contains(name, 'linux') - || _.contains(name, 'ubuntu') - || hasSuffix(name, '.deb') - || hasSuffix(name, '.rpm') - || hasSuffix(name, '.tgz') - || hasSuffix(name, '.tar.gz')) { - - if (_.contains(name, 'linux_deb') || hasSuffix(name, '.deb')) { - prefix = platforms.LINUX_DEB; - } - else if (_.contains(name, 'linux_rpm') || hasSuffix(name, '.rpm')) { - prefix = platforms.LINUX_RPM; - } - else if (_.contains(name, 'linux') || hasSuffix(name, '.tgz') || hasSuffix(name, '.tar.gz')) { - prefix = platforms.LINUX; - } + var name = platform.toLowerCase() + var prefix = "", + suffix = "" + + // Detect NuGet/Squirrel.Windows files + if (name == "releases" || hasSuffix(name, ".nupkg")) + return platforms.WINDOWS_32 + + // Detect prefix: osx, widnows or linux + if (_.contains(name, "win") || hasSuffix(name, ".exe")) + prefix = platforms.WINDOWS + + if ( + _.contains(name, "linux") || + _.contains(name, "ubuntu") || + hasSuffix(name, ".deb") || + hasSuffix(name, ".rpm") || + hasSuffix(name, ".tgz") || + hasSuffix(name, ".tar.gz") + ) { + if (_.contains(name, "linux_deb") || hasSuffix(name, ".deb")) { + prefix = platforms.LINUX_DEB + } else if (_.contains(name, "linux_rpm") || hasSuffix(name, ".rpm")) { + prefix = platforms.LINUX_RPM + } else if ( + _.contains(name, "linux") || + hasSuffix(name, ".tgz") || + hasSuffix(name, ".tar.gz") + ) { + prefix = platforms.LINUX } - - if (_.contains(name, 'mac') - || _.contains(name, 'osx') - || name.indexOf('darwin') >= 0 - || hasSuffix(name, '.dmg')) prefix = platforms.OSX; - - // Detect suffix: 32 or 64 - if (_.contains(name, '32') - || _.contains(name, 'ia32') - || _.contains(name, 'i386')) suffix = '32'; - if (_.contains(name, '64') || _.contains(name, 'x64') || _.contains(name, 'amd64')) suffix = '64'; - - suffix = suffix || (prefix == platforms.OSX? '64' : '32'); - return _.compact([prefix, suffix]).join('_'); + } + + if ( + _.contains(name, "mac") || + _.contains(name, "osx") || + name.indexOf("darwin") >= 0 || + hasSuffix(name, ".dmg") + ) + prefix = platforms.OSX + + // Detect suffix: 32 or 64 + if ( + _.contains(name, "32") || + _.contains(name, "ia32") || + _.contains(name, "i386") + ) + suffix = "32" + if ( + _.contains(name, "64") || + _.contains(name, "x64") || + _.contains(name, "amd64") + ) + suffix = "64" + + suffix = suffix || (prefix == platforms.OSX ? "64" : "32") + return _.compact([prefix, suffix]).join("_") } function hasSuffix(str, suffix) { - return str.slice(str.length-suffix.length) === suffix; + return str.slice(str.length - suffix.length) === suffix } // Satisfies a platform function satisfiesPlatform(platform, list) { - if (_.contains(list, platform)) return true; + if (_.contains(list, platform)) return true - // By default, user 32bits version - if (_.contains(list+'_32', platform)) return true; + // By default, user 32bits version + if (_.contains(list + "_32", platform)) return true - return false; + return false } // Resolve a platform for a version function resolveForVersion(version, platformID, opts) { - opts = _.defaults(opts || {}, { - // Order for filetype - filePreference: ['.exe', '.dmg', '.deb', '.rpm', '.tgz', '.tar.gz', '.zip', '.nupkg'], - wanted: null - }); - - // Prepare file prefs - if (opts.wanted) opts.filePreference = _.uniq([opts.wanted].concat(opts.filePreference)); - - // Normalize platform id - platformID = detectPlatform(platformID); - - return _.chain(version.platforms) - .filter(function(pl) { - return pl.type.indexOf(platformID) === 0; - }) - .sort(function(p1, p2) { - var result = 0; - - // Compare by arhcitecture ("osx_64" > "osx") - if (p1.type.length > p2.type.length) result = -1; - else if (p2.type.length > p1.type.length) result = 1; - - // Order by file type if samee architecture - if (result == 0) { - var ext1 = path.extname(p1.filename); - var ext2 = path.extname(p2.filename); - var pos1 = _.indexOf(opts.filePreference, ext1); - var pos2 = _.indexOf(opts.filePreference, ext2); - - pos1 = pos1 == -1? opts.filePreference.length : pos1; - pos2 = pos2 == -1? opts.filePreference.length : pos2; - - if (pos1 < pos2) result = -1; - else if (pos2 < pos1) result = 1; - } - return result; - }) - .first() - .value() + opts = _.defaults(opts || {}, { + // Order for filetype + filePreference: [ + ".exe", + ".dmg", + ".deb", + ".rpm", + ".tgz", + ".tar.gz", + ".zip", + ".nupkg", + ], + wanted: null, + }) + + // Prepare file prefs + if (opts.wanted) + opts.filePreference = _.uniq([opts.wanted].concat(opts.filePreference)) + + // Normalize platform id + platformID = detectPlatform(platformID) + + return _.chain(version.platforms) + .filter(function (pl) { + return pl.type.indexOf(platformID) === 0 + }) + .sort(function (p1, p2) { + var result = 0 + + // Compare by arhcitecture ("osx_64" > "osx") + if (p1.type.length > p2.type.length) result = -1 + else if (p2.type.length > p1.type.length) result = 1 + + // Order by file type if samee architecture + if (result == 0) { + var ext1 = path.extname(p1.filename) + var ext2 = path.extname(p2.filename) + var pos1 = _.indexOf(opts.filePreference, ext1) + var pos2 = _.indexOf(opts.filePreference, ext2) + + pos1 = pos1 == -1 ? opts.filePreference.length : pos1 + pos2 = pos2 == -1 ? opts.filePreference.length : pos2 + + if (pos1 < pos2) result = -1 + else if (pos2 < pos1) result = 1 + } + return result + }) + .first() + .value() } -module.exports = platforms; -module.exports.detect = detectPlatform; -module.exports.satisfies = satisfiesPlatform; -module.exports.toType = platformToType; -module.exports.resolve = resolveForVersion; +module.exports = platforms +module.exports.detect = detectPlatform +module.exports.satisfies = satisfiesPlatform +module.exports.toType = platformToType +module.exports.resolve = resolveForVersion diff --git a/lib/utils/win-releases.js b/lib/utils/win-releases.js index e468f97..15b4acf 100644 --- a/lib/utils/win-releases.js +++ b/lib/utils/win-releases.js @@ -1,124 +1,115 @@ -var _ = require('lodash'); -var semver = require('semver'); -var stripBom = require('strip-bom'); +var _ = require("lodash") +var semver = require("semver") +var stripBom = require("strip-bom") // Ordered list of supported channel -var CHANNEL_MAGINITUDE = 1000; -var CHANNELS = [ - 'alpha', 'beta', 'unstable', 'rc' -]; +var CHANNEL_MAGINITUDE = 1000 +var CHANNELS = ["alpha", "beta", "unstable", "rc"] // RELEASES parsing -var releaseRe = /^([0-9a-fA-F]{40})\s+(\S+)\s+(\d+)[\r]*$/; - +var releaseRe = /^([0-9a-fA-F]{40})\s+(\S+)\s+(\d+)[\r]*$/ // Hash a prerelease function hashPrerelease(s) { - if (_.isString(s[0])) { - return (_.indexOf(CHANNELS, s[0]) + 1) * CHANNEL_MAGINITUDE + (s[1] || 0); - } else { - return s[0]; - } -}; + if (_.isString(s[0])) { + return (_.indexOf(CHANNELS, s[0]) + 1) * CHANNEL_MAGINITUDE + (s[1] || 0) + } else { + return s[0] + } +} // Map a semver version to a windows version function normVersion(tag) { - var parts = new semver.SemVer(tag); - var prerelease = ""; + var parts = new semver.SemVer(tag) + var prerelease = "" - if (parts.prerelease && parts.prerelease.length > 0) { - prerelease = hashPrerelease(parts.prerelease); - } + if (parts.prerelease && parts.prerelease.length > 0) { + prerelease = hashPrerelease(parts.prerelease) + } - return [ - parts.major, - parts.minor, - parts.patch - ].join('.') + (prerelease? '.'+prerelease : ''); + return ( + [parts.major, parts.minor, parts.patch].join(".") + + (prerelease ? "." + prerelease : "") + ) } // Map a windows version to a semver function toSemver(tag) { - var parts = tag.split('.'); - var version = parts.slice(0, 3).join('.'); - var prerelease = Number(parts[3]); + var parts = tag.split(".") + var version = parts.slice(0, 3).join(".") + var prerelease = Number(parts[3]) - // semver == windows version - if (!prerelease) return version; + // semver == windows version + if (!prerelease) return version - var channelId = Math.floor(prerelease/CHANNEL_MAGINITUDE); - var channel = CHANNELS[channelId - 1]; - var count = prerelease - (channelId*CHANNEL_MAGINITUDE); + var channelId = Math.floor(prerelease / CHANNEL_MAGINITUDE) + var channel = CHANNELS[channelId - 1] + var count = prerelease - channelId * CHANNEL_MAGINITUDE - return version + '-' + channel + '.' + count + return version + "-" + channel + "." + count } // Parse RELEASES file // https://github.com/Squirrel/Squirrel.Windows/blob/0d1250aa6f0c25fe22e92add78af327d1277d97d/src/Squirrel/ReleaseExtensions.cs#L19 function parseRELEASES(content) { - return _.chain(stripBom(content)) - .replace('\r\n', '\n') - .split('\n') - .map(function(line) { - var parts = releaseRe.exec(line); - if (!parts) return null; - - var filename = parts[2]; - var isDelta = filename.indexOf('-full.nupkg') == -1; - - var filenameParts = filename - .replace(".nupkg", "") - .replace("-delta", "") - .replace("-full", "") - .split(/\.|-/) - .reverse(); - - var version = _.chain(filenameParts) - .filter(function(x) { - return /^\d+$/.exec(x); - }) - .reverse() - .value() - .join('.'); - - return { - sha: parts[1], - filename: filename, - size: Number(parts[3]), - isDelta: isDelta, - version: version, - semver: toSemver(version) - }; + return _.chain(stripBom(content)) + .replace("\r\n", "\n") + .split("\n") + .map(function (line) { + var parts = releaseRe.exec(line) + if (!parts) return null + + var filename = parts[2] + var isDelta = filename.indexOf("-full.nupkg") == -1 + + var filenameParts = filename + .replace(".nupkg", "") + .replace("-delta", "") + .replace("-full", "") + .split(/\.|-/) + .reverse() + + var version = _.chain(filenameParts) + .filter(function (x) { + return /^\d+$/.exec(x) }) - .compact() - .value(); + .reverse() + .value() + .join(".") + + return { + sha: parts[1], + filename: filename, + size: Number(parts[3]), + isDelta: isDelta, + version: version, + semver: toSemver(version), + } + }) + .compact() + .value() } // Generate a RELEASES file function generateRELEASES(entries) { - return _.map(entries, function(entry) { - var filename = entry.filename; - - if (!filename) { - filename = [ - entry.app, - entry.version, - entry.isDelta? 'delta.nupkg' : 'full.nupkg' - ].join('-'); - } - - return [ - entry.sha, - filename, - entry.size - ].join(' '); - }) - .join('\n'); + return _.map(entries, function (entry) { + var filename = entry.filename + + if (!filename) { + filename = [ + entry.app, + entry.version, + entry.isDelta ? "delta.nupkg" : "full.nupkg", + ].join("-") + } + + return [entry.sha, filename, entry.size].join(" ") + }).join("\n") } module.exports = { - normVersion: normVersion, - toSemver: toSemver, - parse: parseRELEASES, - generate: generateRELEASES -}; + normVersion: normVersion, + toSemver: toSemver, + parse: parseRELEASES, + generate: generateRELEASES, +} diff --git a/lib/versions.js b/lib/versions.js index 467d96a..4a528d2 100644 --- a/lib/versions.js +++ b/lib/versions.js @@ -1,153 +1,154 @@ -var _ = require('lodash'); -var Q = require('q'); -var semver = require('semver'); +var _ = require("lodash") +var Q = require("q") +var semver = require("semver") -var platforms = require('./utils/platforms'); +var platforms = require("./utils/platforms") // Normalize tag name function normalizeTag(tag) { - if (tag[0] == 'v') tag = tag.slice(1); - return tag; + if (tag[0] == "v") tag = tag.slice(1) + return tag } // Extract channel of version function extractChannel(tag) { - var suffix = tag.split('-')[1]; - if (!suffix) return 'stable'; + var suffix = tag.split("-")[1] + if (!suffix) return "stable" - return suffix.split('.')[0]; + return suffix.split(".")[0] } // Normalize a release to a version function normalizeVersion(release) { - // Ignore draft - if (release.draft) return null; - - var downloadCount = 0; - var releasePlatforms = _.chain(release.assets) - .map(function(asset) { - var platform = platforms.detect(asset.name); - if (!platform) return null; - - downloadCount = downloadCount + asset.download_count; - return { - id: String(asset.id), - type: platform, - filename: asset.name, - size: asset.size, - content_type: asset.content_type, - raw: asset - }; - }) - .compact() - .value(); - - return { - tag: normalizeTag(release.tag_name).split('-')[0], - channel: extractChannel(release.tag_name), - notes: release.body || "", - published_at: new Date(release.published_at), - platforms: releasePlatforms - }; + // Ignore draft + if (release.draft) return null + + var downloadCount = 0 + var releasePlatforms = _.chain(release.assets) + .map(function (asset) { + var platform = platforms.detect(asset.name) + if (!platform) return null + + downloadCount = downloadCount + asset.download_count + return { + id: String(asset.id), + type: platform, + filename: asset.name, + size: asset.size, + content_type: asset.content_type, + raw: asset, + } + }) + .compact() + .value() + + return { + tag: normalizeTag(release.tag_name).split("-")[0], + channel: extractChannel(release.tag_name), + notes: release.body || "", + published_at: new Date(release.published_at), + platforms: releasePlatforms, + } } // Compare two version function compareVersions(v1, v2) { - if (semver.gt(v1.tag, v2.tag)) { - return -1; - } - if (semver.lt(v1.tag, v2.tag)) { - return 1; - } - return 0; + if (semver.gt(v1.tag, v2.tag)) { + return -1 + } + if (semver.lt(v1.tag, v2.tag)) { + return 1 + } + return 0 } function Versions(backend) { - this.backend = backend; - + this.backend = backend } // List versions normalized -Versions.prototype.list = function() { - return this.backend.releases() - .then(function(releases) { - return _.chain(releases) - .map(normalizeVersion) - .compact() - .sort(compareVersions) - .value(); - }); -}; +Versions.prototype.list = function () { + return this.backend.releases().then(function (releases) { + return _.chain(releases) + .map(normalizeVersion) + .compact() + .sort(compareVersions) + .value() + }) +} // Get a specific version by its tag -Versions.prototype.get = function(tag) { - return this.resolve({ - tag: tag - }); -}; +Versions.prototype.get = function (tag) { + return this.resolve({ + tag: tag, + }) +} // Filter versions with criterias -Versions.prototype.filter = function(opts) { - opts = _.defaults(opts || {}, { - tag: 'latest', - platform: null, - channel: 'stable' - }); - if (opts.platform) opts.platform = platforms.detect(opts.platform); - - return this.list() - .then(function(versions) { - return _.chain(versions) - .filter(function(version) { - // Check channel - if (opts.channel != '*' && version.channel != opts.channel) return false; - - // Not available for requested paltform - if (opts.platform && !platforms.satisfies(opts.platform, _.pluck(version.platforms, 'type'))) return false; - - // Check tag satisfies request version - return opts.tag == 'latest' || semver.satisfies(version.tag, opts.tag); - }) - .value(); - }); -}; +Versions.prototype.filter = function (opts) { + opts = _.defaults(opts || {}, { + tag: "latest", + platform: null, + channel: "stable", + }) + if (opts.platform) opts.platform = platforms.detect(opts.platform) + + return this.list().then(function (versions) { + return _.chain(versions) + .filter(function (version) { + // Check channel + if (opts.channel != "*" && version.channel != opts.channel) return false + + // Not available for requested paltform + if ( + opts.platform && + !platforms.satisfies( + opts.platform, + _.pluck(version.platforms, "type"), + ) + ) + return false + + // Check tag satisfies request version + return opts.tag == "latest" || semver.satisfies(version.tag, opts.tag) + }) + .value() + }) +} // Resolve a platform, by filtering then taking the first result -Versions.prototype.resolve = function(opts) { - return this.filter(opts) - .then(function(versions) { - var version = _.first(versions); - if (!version) throw new Error('Version not found: '+opts.tag); +Versions.prototype.resolve = function (opts) { + return this.filter(opts).then(function (versions) { + var version = _.first(versions) + if (!version) throw new Error("Version not found: " + opts.tag) - return version; - }); -}; + return version + }) +} // List all channels from releases -Versions.prototype.channels = function(opts) { - return this.list() - .then(function(versions) { - var channels = {}; - - _.each(versions, function(version) { - if (!channels[version.channel]) { - channels[version.channel] = { - latest: null, - versions_count: 0, - published_at: 0 - }; - } - - channels[version.channel].versions_count += 1; - if (channels[version.channel].published_at < version.published_at) { - channels[version.channel].latest = version.tag; - channels[version.channel].published_at = version.published_at; - } - }); - - return channels; - }); -}; - - -module.exports = Versions; +Versions.prototype.channels = function (opts) { + return this.list().then(function (versions) { + var channels = {} + + _.each(versions, function (version) { + if (!channels[version.channel]) { + channels[version.channel] = { + latest: null, + versions_count: 0, + published_at: 0, + } + } + + channels[version.channel].versions_count += 1 + if (channels[version.channel].published_at < version.published_at) { + channels[version.channel].latest = version.tag + channels[version.channel].published_at = version.published_at + } + }) + + return channels + }) +} + +module.exports = Versions diff --git a/test/platforms.js b/test/platforms.js index 7aadaec..b9b0115 100644 --- a/test/platforms.js +++ b/test/platforms.js @@ -1,167 +1,216 @@ -require('should'); -var platforms = require('../lib/utils/platforms'); - -describe('Platforms', function() { - - describe('Detect', function() { - - it('should detect osx_64', function() { - platforms.detect('myapp-v0.25.1-darwin-x64.zip').should.be.exactly(platforms.OSX_64); - platforms.detect('myapp.dmg').should.be.exactly(platforms.OSX_64); - }); - - it('should detect windows_32', function() { - platforms.detect('myapp-v0.25.1-win32-ia32.zip').should.be.exactly(platforms.WINDOWS_32); - platforms.detect('atom-1.0.9-delta.nupkg').should.be.exactly(platforms.WINDOWS_32); - platforms.detect('RELEASES').should.be.exactly(platforms.WINDOWS_32); - }); - - it('should detect linux', function() { - platforms.detect('enterprise-amd64.tar.gz').should.be.exactly(platforms.LINUX_64); - platforms.detect('enterprise-amd64.tgz').should.be.exactly(platforms.LINUX_64); - platforms.detect('enterprise-ia32.tar.gz').should.be.exactly(platforms.LINUX_32); - platforms.detect('enterprise-ia32.tgz').should.be.exactly(platforms.LINUX_32); - }); - - it('should detect debian_32', function() { - platforms.detect('atom-ia32.deb').should.be.exactly(platforms.LINUX_DEB_32); - }); - - it('should detect debian_64', function() { - platforms.detect('atom-amd64.deb').should.be.exactly(platforms.LINUX_DEB_64); - }); - - it('should detect rpm_32', function() { - platforms.detect('atom-ia32.rpm').should.be.exactly(platforms.LINUX_RPM_32); - }); - - it('should detect rpm_64', function() { - platforms.detect('atom-amd64.rpm').should.be.exactly(platforms.LINUX_RPM_64); - }); - - }); - - describe('Resolve', function() { - var version = { - platforms: [ - { - 'type': 'osx_64', - 'filename': 'test-3.3.1-darwin.dmg', - 'download_url': 'https://api.github.com/repos/test/test2/releases/assets/793838', - 'download_count': 2 - }, - { - 'type': 'osx_64', - 'filename': 'test-3.3.1-darwin-x64.zip', - 'download_url': 'https://api.github.com/repos/test/test2/releases/assets/793869', - 'download_count': 0 - }, - { - 'type': 'windows_32', - 'filename': 'atom-1.0.9-delta.nupkg', - 'size': 1457531, - 'content_type': 'application/zip', - 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825732', - 'download_count': 55844 - }, - { - 'type': 'windows_32', - 'filename': 'atom-1.0.9-full.nupkg', - 'size': 78181725, - 'content_type': 'application/zip', - 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825730', - 'download_count': 26987 - }, - { - 'type': 'linux_32', - 'filename': 'atom-ia32.tar.gz', - 'size': 71292506, - 'content_type': 'application/zip', - 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', - 'download_count': 2494 - }, - { - 'type': 'linux_64', - 'filename': 'atom-amd64.tar.gz', - 'size': 71292506, - 'content_type': 'application/zip', - 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', - 'download_count': 2494 - }, - { - 'type': 'linux_rpm_32', - 'filename': 'atom-ia32.rpm', - 'size': 71292506, - 'content_type': 'application/zip', - 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', - 'download_count': 2494 - }, - { - 'type': 'linux_rpm_64', - 'filename': 'atom-amd64.rpm', - 'size': 71292506, - 'content_type': 'application/zip', - 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', - 'download_count': 2494 - }, - { - 'type': 'linux_deb_32', - 'filename': 'atom-ia32.deb', - 'size': 71292506, - 'content_type': 'application/zip', - 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', - 'download_count': 2494 - }, - { - 'type': 'linux_deb_64', - 'filename': 'atom-amd64.deb', - 'size': 71292506, - 'content_type': 'application/zip', - 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', - 'download_count': 2494 - }, - { - 'type': 'windows_32', - 'filename': 'atom-windows.zip', - 'size': 79815714, - 'content_type': 'application/zip', - 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825729', - 'download_count': 463 - }, - { - 'type': 'windows_32', - 'filename': 'AtomSetup.exe', - 'size': 78675720, - 'content_type': 'application/zip', - 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825728', - 'download_count': 5612 - } - ] - }; - - - it('should resolve to best platform', function() { - platforms.resolve(version, 'osx').filename.should.be.exactly('test-3.3.1-darwin.dmg'); - platforms.resolve(version, 'win32').filename.should.be.exactly('AtomSetup.exe'); - platforms.resolve(version, 'linux_64').filename.should.be.exactly('atom-amd64.tar.gz'); - platforms.resolve(version, 'linux_32').filename.should.be.exactly('atom-ia32.tar.gz'); - platforms.resolve(version, 'linux_rpm_32').filename.should.be.exactly('atom-ia32.rpm'); - platforms.resolve(version, 'linux_rpm_64').filename.should.be.exactly('atom-amd64.rpm'); - platforms.resolve(version, 'linux_deb_32').filename.should.be.exactly('atom-ia32.deb'); - platforms.resolve(version, 'linux_deb_64').filename.should.be.exactly('atom-amd64.deb'); - }); - - it('should resolve to best platform with a preferred filetype', function() { - platforms.resolve(version, 'osx', { - filePreference: ['.zip'] - }).filename.should.be.exactly('test-3.3.1-darwin-x64.zip'); - }); - - it('should resolve to best platform with a wanted filetype', function() { - platforms.resolve(version, 'osx', { - wanted: '.zip' - }).filename.should.be.exactly('test-3.3.1-darwin-x64.zip'); - }); - }); - -}); +require("should") +var platforms = require("../lib/utils/platforms") + +describe("Platforms", function () { + describe("Detect", function () { + it("should detect osx_64", function () { + platforms + .detect("myapp-v0.25.1-darwin-x64.zip") + .should.be.exactly(platforms.OSX_64) + platforms.detect("myapp.dmg").should.be.exactly(platforms.OSX_64) + }) + + it("should detect windows_32", function () { + platforms + .detect("myapp-v0.25.1-win32-ia32.zip") + .should.be.exactly(platforms.WINDOWS_32) + platforms + .detect("atom-1.0.9-delta.nupkg") + .should.be.exactly(platforms.WINDOWS_32) + platforms.detect("RELEASES").should.be.exactly(platforms.WINDOWS_32) + }) + + it("should detect linux", function () { + platforms + .detect("enterprise-amd64.tar.gz") + .should.be.exactly(platforms.LINUX_64) + platforms + .detect("enterprise-amd64.tgz") + .should.be.exactly(platforms.LINUX_64) + platforms + .detect("enterprise-ia32.tar.gz") + .should.be.exactly(platforms.LINUX_32) + platforms + .detect("enterprise-ia32.tgz") + .should.be.exactly(platforms.LINUX_32) + }) + + it("should detect debian_32", function () { + platforms + .detect("atom-ia32.deb") + .should.be.exactly(platforms.LINUX_DEB_32) + }) + + it("should detect debian_64", function () { + platforms + .detect("atom-amd64.deb") + .should.be.exactly(platforms.LINUX_DEB_64) + }) + + it("should detect rpm_32", function () { + platforms + .detect("atom-ia32.rpm") + .should.be.exactly(platforms.LINUX_RPM_32) + }) + + it("should detect rpm_64", function () { + platforms + .detect("atom-amd64.rpm") + .should.be.exactly(platforms.LINUX_RPM_64) + }) + }) + + describe("Resolve", function () { + var version = { + platforms: [ + { + type: "osx_64", + filename: "test-3.3.1-darwin.dmg", + download_url: + "https://api.github.com/repos/test/test2/releases/assets/793838", + download_count: 2, + }, + { + type: "osx_64", + filename: "test-3.3.1-darwin-x64.zip", + download_url: + "https://api.github.com/repos/test/test2/releases/assets/793869", + download_count: 0, + }, + { + type: "windows_32", + filename: "atom-1.0.9-delta.nupkg", + size: 1457531, + content_type: "application/zip", + download_url: + "https://api.github.com/repos/atom/atom/releases/assets/825732", + download_count: 55844, + }, + { + type: "windows_32", + filename: "atom-1.0.9-full.nupkg", + size: 78181725, + content_type: "application/zip", + download_url: + "https://api.github.com/repos/atom/atom/releases/assets/825730", + download_count: 26987, + }, + { + type: "linux_32", + filename: "atom-ia32.tar.gz", + size: 71292506, + content_type: "application/zip", + download_url: + "https://api.github.com/repos/atom/atom/releases/assets/825658", + download_count: 2494, + }, + { + type: "linux_64", + filename: "atom-amd64.tar.gz", + size: 71292506, + content_type: "application/zip", + download_url: + "https://api.github.com/repos/atom/atom/releases/assets/825658", + download_count: 2494, + }, + { + type: "linux_rpm_32", + filename: "atom-ia32.rpm", + size: 71292506, + content_type: "application/zip", + download_url: + "https://api.github.com/repos/atom/atom/releases/assets/825658", + download_count: 2494, + }, + { + type: "linux_rpm_64", + filename: "atom-amd64.rpm", + size: 71292506, + content_type: "application/zip", + download_url: + "https://api.github.com/repos/atom/atom/releases/assets/825658", + download_count: 2494, + }, + { + type: "linux_deb_32", + filename: "atom-ia32.deb", + size: 71292506, + content_type: "application/zip", + download_url: + "https://api.github.com/repos/atom/atom/releases/assets/825658", + download_count: 2494, + }, + { + type: "linux_deb_64", + filename: "atom-amd64.deb", + size: 71292506, + content_type: "application/zip", + download_url: + "https://api.github.com/repos/atom/atom/releases/assets/825658", + download_count: 2494, + }, + { + type: "windows_32", + filename: "atom-windows.zip", + size: 79815714, + content_type: "application/zip", + download_url: + "https://api.github.com/repos/atom/atom/releases/assets/825729", + download_count: 463, + }, + { + type: "windows_32", + filename: "AtomSetup.exe", + size: 78675720, + content_type: "application/zip", + download_url: + "https://api.github.com/repos/atom/atom/releases/assets/825728", + download_count: 5612, + }, + ], + } + + it("should resolve to best platform", function () { + platforms + .resolve(version, "osx") + .filename.should.be.exactly("test-3.3.1-darwin.dmg") + platforms + .resolve(version, "win32") + .filename.should.be.exactly("AtomSetup.exe") + platforms + .resolve(version, "linux_64") + .filename.should.be.exactly("atom-amd64.tar.gz") + platforms + .resolve(version, "linux_32") + .filename.should.be.exactly("atom-ia32.tar.gz") + platforms + .resolve(version, "linux_rpm_32") + .filename.should.be.exactly("atom-ia32.rpm") + platforms + .resolve(version, "linux_rpm_64") + .filename.should.be.exactly("atom-amd64.rpm") + platforms + .resolve(version, "linux_deb_32") + .filename.should.be.exactly("atom-ia32.deb") + platforms + .resolve(version, "linux_deb_64") + .filename.should.be.exactly("atom-amd64.deb") + }) + + it("should resolve to best platform with a preferred filetype", function () { + platforms + .resolve(version, "osx", { + filePreference: [".zip"], + }) + .filename.should.be.exactly("test-3.3.1-darwin-x64.zip") + }) + + it("should resolve to best platform with a wanted filetype", function () { + platforms + .resolve(version, "osx", { + wanted: ".zip", + }) + .filename.should.be.exactly("test-3.3.1-darwin-x64.zip") + }) + }) +}) diff --git a/test/win-releases.js b/test/win-releases.js index e06fac0..12f6069 100644 --- a/test/win-releases.js +++ b/test/win-releases.js @@ -1,104 +1,108 @@ -require('should'); -var winReleases = require('../lib/utils/win-releases'); - -describe('Windows RELEASES', function() { - - describe('Version Normalization', function() { - - it('should not changed version without pre-release', function() { - winReleases.normVersion('1.0.0').should.be.exactly('1.0.0'); - winReleases.normVersion('4.5.0').should.be.exactly('4.5.0'); - winReleases.normVersion('67.8.345').should.be.exactly('67.8.345'); - }); - - it('should normalize the pre-release', function() { - winReleases.normVersion('1.0.0-alpha.1').should.be.exactly('1.0.0.1001'); - winReleases.normVersion('1.0.0-beta.1').should.be.exactly('1.0.0.2001'); - winReleases.normVersion('1.0.0-unstable.1').should.be.exactly('1.0.0.3001'); - winReleases.normVersion('1.0.0-rc.1').should.be.exactly('1.0.0.4001'); - winReleases.normVersion('1.0.0-14').should.be.exactly('1.0.0.14'); - }); - - it('should correctly return to a semver', function() { - winReleases.toSemver('1.0.0.1001').should.be.exactly('1.0.0-alpha.1'); - winReleases.toSemver('1.0.0.2001').should.be.exactly('1.0.0-beta.1'); - winReleases.toSemver('1.0.0.2015').should.be.exactly('1.0.0-beta.15'); - winReleases.toSemver('1.0.0').should.be.exactly('1.0.0'); - }); - - }); - - describe('Parsing', function() { - var releases = winReleases.parse( - '62E8BF432F29E8E08240910B85EDBF2D1A41EDF2 atom-0.178.0-full.nupkg 81272434\n' + - '5D754139E89802E88984185D2276B54DB730CD5E atom-0.178.1-delta.nupkg 8938535\n' + - 'DD48D16EE177DD278F0A82CDDB72EBD043C767D2 atom-0.178.1-full.nupkg 81293415\n' + - '02D56FF2DD6CB8FE059167E227433078CDAF5630 atom-0.179.0-delta.nupkg 9035217\n' + - '8F5FDFD0BD81475EAD95E9E415579A852476E5FC atom-0.179.0-full.nupkg 81996151' - ); - - it('should have parsed all lines', function() { - releases.should.be.an.Array(); - releases.length.should.be.exactly(5); - }); - - it('should parse a one-line file (with utf-8 BOM)', function() { - var oneRelease = winReleases.parse('\uFEFF24182FAD211FB9EB72610B1C086810FE37F70AE3 gitbook-editor-4.0.0-full.nupkg 46687158'); - oneRelease.length.should.be.exactly(1); - }); - - it('should correctly parse sha, version, isDelta, filename and size', function() { - releases[0].sha.should.be.a.String(); - releases[0].sha.should.be.exactly('62E8BF432F29E8E08240910B85EDBF2D1A41EDF2'); - - releases[0].filename.should.be.a.String(); - releases[0].filename.should.be.exactly('atom-0.178.0-full.nupkg'); - - releases[0].size.should.be.a.Number(); - releases[0].size.should.be.exactly(81272434); - - releases[0].isDelta.should.be.a.Boolean(); - releases[0].version.should.be.a.String(); - }); - - it('should correctly detect deltas', function() { - releases[0].isDelta.should.be.False(); - releases[1].isDelta.should.be.True(); - }); - - it('should correctly parse versions', function() { - releases[0].version.should.be.exactly('0.178.0'); - releases[1].version.should.be.exactly('0.178.1'); - }); - - }); - - describe('Generations', function() { - var input = '62E8BF432F29E8E08240910B85EDBF2D1A41EDF2 atom-0.178.0-full.nupkg 81272434\n' + - '5D754139E89802E88984185D2276B54DB730CD5E atom-0.178.1-delta.nupkg 8938535\n' + - 'DD48D16EE177DD278F0A82CDDB72EBD043C767D2 atom-0.178.1-full.nupkg 81293415\n' + - '02D56FF2DD6CB8FE059167E227433078CDAF5630 atom-0.179.0-delta.nupkg 9035217\n' + - '8F5FDFD0BD81475EAD95E9E415579A852476E5FC atom-0.179.0-full.nupkg 81996151'; - - var releases = winReleases.parse(input); - - - it('should correctly generate a RELEASES file', function() { - winReleases.generate(releases).should.be.exactly(input); - }); - - it('should correctly generate filenames', function() { - winReleases.generate([ - { - sha: '62E8BF432F29E8E08240910B85EDBF2D1A41EDF2', - version: '1.0.0', - app: 'atom', - size: 81272434, - isDelta: false - } - ]).should.be.exactly('62E8BF432F29E8E08240910B85EDBF2D1A41EDF2 atom-1.0.0-full.nupkg 81272434'); - }); - - }); - -}); +require("should") +var winReleases = require("../lib/utils/win-releases") + +describe("Windows RELEASES", function () { + describe("Version Normalization", function () { + it("should not changed version without pre-release", function () { + winReleases.normVersion("1.0.0").should.be.exactly("1.0.0") + winReleases.normVersion("4.5.0").should.be.exactly("4.5.0") + winReleases.normVersion("67.8.345").should.be.exactly("67.8.345") + }) + + it("should normalize the pre-release", function () { + winReleases.normVersion("1.0.0-alpha.1").should.be.exactly("1.0.0.1001") + winReleases.normVersion("1.0.0-beta.1").should.be.exactly("1.0.0.2001") + winReleases + .normVersion("1.0.0-unstable.1") + .should.be.exactly("1.0.0.3001") + winReleases.normVersion("1.0.0-rc.1").should.be.exactly("1.0.0.4001") + winReleases.normVersion("1.0.0-14").should.be.exactly("1.0.0.14") + }) + + it("should correctly return to a semver", function () { + winReleases.toSemver("1.0.0.1001").should.be.exactly("1.0.0-alpha.1") + winReleases.toSemver("1.0.0.2001").should.be.exactly("1.0.0-beta.1") + winReleases.toSemver("1.0.0.2015").should.be.exactly("1.0.0-beta.15") + winReleases.toSemver("1.0.0").should.be.exactly("1.0.0") + }) + }) + + describe("Parsing", function () { + var releases = winReleases.parse( + "62E8BF432F29E8E08240910B85EDBF2D1A41EDF2 atom-0.178.0-full.nupkg 81272434\n" + + "5D754139E89802E88984185D2276B54DB730CD5E atom-0.178.1-delta.nupkg 8938535\n" + + "DD48D16EE177DD278F0A82CDDB72EBD043C767D2 atom-0.178.1-full.nupkg 81293415\n" + + "02D56FF2DD6CB8FE059167E227433078CDAF5630 atom-0.179.0-delta.nupkg 9035217\n" + + "8F5FDFD0BD81475EAD95E9E415579A852476E5FC atom-0.179.0-full.nupkg 81996151", + ) + + it("should have parsed all lines", function () { + releases.should.be.an.Array() + releases.length.should.be.exactly(5) + }) + + it("should parse a one-line file (with utf-8 BOM)", function () { + var oneRelease = winReleases.parse( + "\uFEFF24182FAD211FB9EB72610B1C086810FE37F70AE3 gitbook-editor-4.0.0-full.nupkg 46687158", + ) + oneRelease.length.should.be.exactly(1) + }) + + it("should correctly parse sha, version, isDelta, filename and size", function () { + releases[0].sha.should.be.a.String() + releases[0].sha.should.be.exactly( + "62E8BF432F29E8E08240910B85EDBF2D1A41EDF2", + ) + + releases[0].filename.should.be.a.String() + releases[0].filename.should.be.exactly("atom-0.178.0-full.nupkg") + + releases[0].size.should.be.a.Number() + releases[0].size.should.be.exactly(81272434) + + releases[0].isDelta.should.be.a.Boolean() + releases[0].version.should.be.a.String() + }) + + it("should correctly detect deltas", function () { + releases[0].isDelta.should.be.False() + releases[1].isDelta.should.be.True() + }) + + it("should correctly parse versions", function () { + releases[0].version.should.be.exactly("0.178.0") + releases[1].version.should.be.exactly("0.178.1") + }) + }) + + describe("Generations", function () { + var input = + "62E8BF432F29E8E08240910B85EDBF2D1A41EDF2 atom-0.178.0-full.nupkg 81272434\n" + + "5D754139E89802E88984185D2276B54DB730CD5E atom-0.178.1-delta.nupkg 8938535\n" + + "DD48D16EE177DD278F0A82CDDB72EBD043C767D2 atom-0.178.1-full.nupkg 81293415\n" + + "02D56FF2DD6CB8FE059167E227433078CDAF5630 atom-0.179.0-delta.nupkg 9035217\n" + + "8F5FDFD0BD81475EAD95E9E415579A852476E5FC atom-0.179.0-full.nupkg 81996151" + + var releases = winReleases.parse(input) + + it("should correctly generate a RELEASES file", function () { + winReleases.generate(releases).should.be.exactly(input) + }) + + it("should correctly generate filenames", function () { + winReleases + .generate([ + { + sha: "62E8BF432F29E8E08240910B85EDBF2D1A41EDF2", + version: "1.0.0", + app: "atom", + size: 81272434, + isDelta: false, + }, + ]) + .should.be.exactly( + "62E8BF432F29E8E08240910B85EDBF2D1A41EDF2 atom-1.0.0-full.nupkg 81272434", + ) + }) + }) +}) From 95ec5214209893facf3be593e791f0f962df06b2 Mon Sep 17 00:00:00 2001 From: Ben Williams Date: Wed, 17 Mar 2021 01:55:47 -0700 Subject: [PATCH 7/7] fixed merge --- lib/nuts.js | 9 +++++++++ test/platforms.js | 6 ------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/nuts.js b/lib/nuts.js index 1ed06a7..ad7f4be 100644 --- a/lib/nuts.js +++ b/lib/nuts.js @@ -36,8 +36,17 @@ function Nuts(opts) { // Secret for GitHub webhook refreshSecret: "secret", + + // Prefix for all routes + routePrefix: "/", }) + if ( + this.opts.routePrefix.substr(this.opts.routePrefix.length - 1, 1) !== "/" + ) { + throw new Error("ROUTE_PREIX must end with a slash") + } + // .init() is now a memoized version of ._init() this.init = _.memoize(this._init) diff --git a/test/platforms.js b/test/platforms.js index 9e15a29..54667b7 100644 --- a/test/platforms.js +++ b/test/platforms.js @@ -1,9 +1,6 @@ require("should") var platforms = require("../lib/utils/platforms") -<<<<<<< HEAD -======= var useragent = require("express-useragent") ->>>>>>> a964761422f24d5440e9ab2e48dc25c070616e14 describe("Platforms", function () { describe("Detect", function () { @@ -24,8 +21,6 @@ describe("Platforms", function () { platforms.detect("RELEASES").should.be.exactly(platforms.WINDOWS_32) }) -<<<<<<< HEAD -======= it("should detect windows_64", function () { platforms.detect("MyApp-x64.exe").should.be.exactly(platforms.WINDOWS_64) var chrome = useragent.parse( @@ -42,7 +37,6 @@ describe("Platforms", function () { .should.be.exactly(platforms.WINDOWS_64) }) ->>>>>>> a964761422f24d5440e9ab2e48dc25c070616e14 it("should detect linux", function () { platforms .detect("enterprise-amd64.tar.gz")