├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── docs ├── 1.x │ └── api-and-usage.md └── 2.x │ ├── api.md │ └── how-to.md ├── gulpfile.js ├── package.json ├── src └── GhReleases.js └── test ├── app ├── main.js └── package.json └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["add-module-exports"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | GhReleases.js 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenslind/electron-gh-releases/e57d78b652558f59d50ccebb05390ef284fbbdbe/.npmignore -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | script: npm run travis 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jens Lind 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Deprecated** - see [#28](https://github.com/jenslind/electron-gh-releases/issues/28) and [update.electronjs.org](https://update.electronjs.org) 2 | 3 | # Electron-gh-releases [![Build Status](https://travis-ci.org/jenslind/electron-gh-releases.svg?branch=master)](https://travis-ci.org/jenslind/electron-gh-releases) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) 4 | > Auto-update for electron apps using Github releases together with the built-in [auto-updater](https://github.com/atom/electron/blob/master/docs/api/auto-updater.md). 5 | 6 | *Note:* In `2.x` there is now support for Windows. 7 | 8 | ## Install 9 | 10 | ``` 11 | npm install --save electron-gh-releases 12 | ``` 13 | 14 | ## Usage 15 | 16 | [Usage example and API docs](https://github.com/jenslind/electron-gh-releases/tree/master/docs/2.x) 17 | 18 | ## Tests 19 | 20 | ``` 21 | npm test 22 | ``` 23 | 24 | ## License 25 | MIT 26 | -------------------------------------------------------------------------------- /docs/1.x/api-and-usage.md: -------------------------------------------------------------------------------- 1 | # v1.x API and usage 2 | 3 | Please note that 1.x does not support Windows, only OS X apps is supported. 4 | 5 | ## Usage 6 | 7 | #### auto_updater.json 8 | 9 | A file named `auto_updater.json` needs to be placed in the root of your repo. 10 | 11 | This file should contain at least a `url` key, pointing to the `.zip` file URL in your latest release. 12 | Look [here](https://github.com/atom/electron/blob/master/docs/api/auto-updater.md#update-json-format) for valid keys. 13 | 14 | #### When publishing a new release on Github 15 | 16 | 1. The tag needs to be a valid semver version. 17 | 2. Your `.app` must be [signed](https://github.com/atom/electron/blob/master/docs/api/auto-updater.md#auto-updater) and `zip` compressed. 18 | 19 | #### Checking and installing updates 20 | 21 | ```javascript 22 | var gh_releases = require('electron-gh-releases') 23 | 24 | var options = { 25 | repo: 'jenslind/electron-gh-releases', 26 | currentVersion: app.getVersion() 27 | } 28 | 29 | var update = new gh_releases(options, function (auto_updater) { 30 | // Auto updater event listener 31 | auto_updater.on('update-downloaded', function (e, rNotes, rName, rDate, uUrl, quitAndUpdate) { 32 | // Install the update 33 | quitAndUpdate() 34 | }) 35 | }) 36 | 37 | // Check for updates 38 | update.check(function (err, status) { 39 | if (!err && status) { 40 | update.download() 41 | } 42 | }) 43 | ``` 44 | 45 | ## Docs 46 | 47 | ### Constructor 48 | 49 | #### new gh_releases([options], [callback]) 50 | 51 | ##### options 52 | 53 | `repo` - **String** Your github repo in the format: USERNAME/REPO_NAME 54 | 55 | `currentVersion` - **Semver version** 56 | 57 | ##### callback(auto_updater) 58 | 59 | Returns the auto_updater instance. 60 | 61 | ### Methods 62 | 63 | #### .check([callback]) 64 | > Checks for new releases on Github. 65 | 66 | ##### callback(err, status) 67 | `err` - **String** Contains errors, if any. 68 | 69 | `status` - **Boolean** Is true if a new version is available. 70 | 71 | #### .download() 72 | > Runs Electrons [checkForUpdates()](https://github.com/atom/electron/blob/master/docs/api/auto-updater.md#autoupdatercheckforupdates) method. This method should only be called if check() returns true. 73 | -------------------------------------------------------------------------------- /docs/2.x/api.md: -------------------------------------------------------------------------------- 1 | ## Usage example 2 | 3 | ```javascript 4 | const GhReleases = require('electron-gh-releases') 5 | 6 | let options = { 7 | repo: 'jenslind/electron-gh-releases', 8 | currentVersion: app.getVersion() 9 | } 10 | 11 | const updater = new GhReleases(options) 12 | 13 | // Check for updates 14 | // `status` returns true if there is a new update available 15 | updater.check((err, status) => { 16 | if (!err && status) { 17 | // Download the update 18 | updater.download() 19 | } 20 | }) 21 | 22 | // When an update has been downloaded 23 | updater.on('update-downloaded', (info) => { 24 | // Restart the app and install the update 25 | updater.install() 26 | }) 27 | 28 | // Access electrons autoUpdater 29 | updater.autoUpdater 30 | ``` 31 | 32 | ## Docs 33 | 34 | ### GhRealeases([options]) 35 | 36 | #### `options` 37 | 38 | `repo` - **String** Your github repo in the format: USERNAME/REPO_NAME 39 | 40 | `currentVersion` - **Semver version** The current version of the running app. 41 | 42 | ### Methods 43 | 44 | #### `.check([callback])` 45 | > Checks if there is a new release on GitHub. 46 | 47 | ##### callback(err, status) 48 | `err` - **String** Contains errors, if any. 49 | 50 | `status` - **Boolean** Is true if a new version is available. 51 | 52 | #### `.download()` 53 | > Runs Electrons [checkForUpdates()](https://github.com/atom/electron/blob/master/docs/api/auto-updater.md#autoupdatercheckforupdates) method. This method should only be called if check() returns true. 54 | 55 | #### `.install()` 56 | > Runs Electrons [quitAndInstall()](https://github.com/atom/electron/blob/master/docs/api/auto-updater.md#autoupdaterquitandinstall) method. This method should only be called when you know a new version has been downloaded. 57 | 58 | ### Events 59 | 60 | #### `update-downloaded` 61 | > Will emit when a new update has ben downloaded. 62 | -------------------------------------------------------------------------------- /docs/2.x/how-to.md: -------------------------------------------------------------------------------- 1 | ## How it works 2 | Electrons autoUpdater uses Squirrel. There is some notable differences between how Squirrel works on Windows and Mac. 3 | 4 | ### On Mac 5 | On Mac, Squirrel does not do any version comparison checks on the client side. 6 | It depends on a server, therefore we need a "server". This is solved by putting a `json` file in your GitHub repository that will act as our server. 7 | 8 | #### auto_updater.json 9 | A file named `auto_updater.json` needs to be placed in the root of your repo. 10 | 11 | This file should contain at least a `url` key, pointing to the `.zip` file URL in your latest release. 12 | Look [here](https://github.com/Squirrel/Squirrel.Mac#update-json-format) for valid keys. 13 | 14 | ### On Windows 15 | Squirrel does not require a server on Windows. So no need to keep an `auto_updater.json` updated for the Windows version of your app. 16 | 17 | 18 | ## Publishing a new release on Github 19 | When you create a new release on GitHub this is what you should think of: 20 | 21 | - The tag needs to be a valid `semver` version. 22 | 23 | **Mac apps:** 24 | - Your `.app` must be [signed](https://github.com/atom/electron/blob/master/docs/api/auto-updater.md#auto-updater) and `zip` compressed. 25 | - Update your `auto_updater.json` file to point to the newly uploaded zipped `.app`. 26 | 27 | **Windows apps:** 28 | - Use [grunt-electron-installer](https://github.com/atom/grunt-electron-installer) to create a new installer for your app. Then upload the `RELEASE` and `.nupkg` files as assets to the release. 29 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var babel = require('gulp-babel') 3 | var watch = require('gulp-watch') 4 | 5 | gulp.task('watch', function () { 6 | return gulp.src('src/*') 7 | .pipe(watch('src/*')) 8 | .pipe(babel()) 9 | .pipe(gulp.dest('./')) 10 | }) 11 | 12 | gulp.task('build', function () { 13 | return gulp.src('src/*') 14 | .pipe(babel()) 15 | .pipe(gulp.dest('./')) 16 | }) 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-gh-releases", 3 | "version": "2.0.4", 4 | "description": "Electron auto-update by releasing on Github.", 5 | "main": "./GhReleases.js", 6 | "devDependencies": { 7 | "babel-plugin-add-module-exports": "^0.1.2", 8 | "babel-preset-es2015": "^6.6.0", 9 | "electron-prebuilt": "^0.37.3", 10 | "gulp": "^3.9.1", 11 | "gulp-babel": "^6.1.2", 12 | "gulp-watch": "^4.3.5", 13 | "mocha": "^2.4.5", 14 | "standard": "^6.0.8" 15 | }, 16 | "dependencies": { 17 | "got": "^5.1.0", 18 | "semver": "^5.1.0" 19 | }, 20 | "scripts": { 21 | "test": "standard src/*.js && electron test/app", 22 | "travis": "standard src/*.js", 23 | "prepublish": "gulp build" 24 | }, 25 | "author": "Jens Lind (jenslind.com)", 26 | "license": "MIT", 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/jenslind/electron-gh-releases.git" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/GhReleases.js: -------------------------------------------------------------------------------- 1 | const semver = require('semver') 2 | const autoUpdater = require('electron').autoUpdater 3 | const got = require('got') 4 | const events = require('events') 5 | 6 | const WIN32 = (process.platform === 'win32') 7 | const DARWIN = (process.platform === 'darwin') 8 | const REGEX_ZIP_URL = /\/(v)?(\d+\.\d+\.\d+)\/.*\.zip/ 9 | 10 | export default class GhReleases extends events.EventEmitter { 11 | 12 | constructor (gh) { 13 | super() 14 | 15 | let self = this 16 | 17 | self.repo = gh.repo 18 | self.repoUrl = 'https://github.com/' + gh.repo 19 | self.currentVersion = gh.currentVersion 20 | self.autoUpdater = autoUpdater 21 | 22 | self.autoUpdater.on('update-downloaded', (...args) => self.emit('update-downloaded', args)) 23 | } 24 | 25 | /** 26 | * Get tags from this.repo 27 | */ 28 | _getLatestTag () { 29 | let url = this.repoUrl + '/releases/latest' 30 | return got.head(url) 31 | .then(res => { 32 | let latestTag = res.socket._httpMessage.path.split('/').pop() 33 | return latestTag 34 | }) 35 | .catch(err => { 36 | if (err) throw new Error('Unable to get latest release tag from Github.') 37 | }) 38 | } 39 | 40 | /** 41 | * Get current version from app. 42 | */ 43 | _getCurrentVersion () { 44 | return this.currentVersion 45 | } 46 | 47 | /** 48 | * Compare current with the latest version. 49 | */ 50 | _newVersion (latest) { 51 | return semver.lt(this._getCurrentVersion(), latest) 52 | } 53 | 54 | /** 55 | * Get the feed URL from this.repo 56 | */ 57 | _getFeedUrl (tag) { 58 | let feedUrl 59 | 60 | // If on Windows 61 | if (WIN32) { 62 | return new Promise((resolve, reject) => { 63 | feedUrl = this.repoUrl + '/releases/download/' + tag 64 | resolve(feedUrl) 65 | }) 66 | } 67 | 68 | // On Mac we need to use the `auto_updater.json` 69 | feedUrl = 'https://raw.githubusercontent.com/' + this.repo + '/master/auto_updater.json' 70 | 71 | // Make sure feedUrl exists 72 | return got.get(feedUrl) 73 | .then(res => { 74 | if (res.statusCode === 404) { 75 | throw new Error('auto_updater.json does not exist.') 76 | } else if (res.statusCode !== 200) { 77 | throw new Error('Unable to fetch auto_updater.json: ' + res.body) 78 | } 79 | 80 | let zipUrl 81 | try { 82 | zipUrl = JSON.parse(res.body).url 83 | } catch (err) { 84 | throw new Error('Unable to parse the auto_updater.json: ' + err.message + ', body: ' + res.body) 85 | } 86 | 87 | const matchReleaseUrl = zipUrl.match(REGEX_ZIP_URL) 88 | if (!matchReleaseUrl) { 89 | throw new Error('The zipUrl (' + zipUrl + ') is a invalid release URL') 90 | } 91 | 92 | const versionInZipUrl = matchReleaseUrl[matchReleaseUrl.length -1] 93 | const latestVersion = semver.clean(tag) 94 | if (versionInZipUrl !== latestVersion) { 95 | throw new Error('The feedUrl does not link to latest tag (zipUrl=' + versionInZipUrl + '; latestVersion=' + latestVersion + ')') 96 | } 97 | 98 | return feedUrl 99 | }) 100 | } 101 | 102 | /** 103 | * Check for updates. 104 | */ 105 | check (cb) { 106 | let self = this 107 | 108 | // Get latest released version from Github. 109 | this._getLatestTag() 110 | .then(tag => { 111 | // Check if tag is valid semver 112 | if (!tag || !semver.valid(semver.clean(tag))) { 113 | throw new Error('Could not find a valid release tag.') 114 | } 115 | 116 | // Compare with current version. 117 | if (!self._newVersion(tag)) { 118 | throw new Error('There is no newer version.') 119 | } 120 | 121 | // There is a new version! 122 | // Get feed url from gh repo. 123 | return self._getFeedUrl(tag) 124 | }) 125 | .then(feedUrl => { 126 | if (!DARWIN && !WIN32) return cb(new Error('This platform is not supported.'), true) 127 | 128 | // Set feedUrl in auto_updater. 129 | this.autoUpdater.setFeedURL(feedUrl) 130 | 131 | cb(null, true) 132 | }) 133 | .catch(err => { 134 | cb(err || null, false) 135 | }) 136 | } 137 | 138 | /** 139 | * Download latest release. 140 | */ 141 | download () { 142 | // Run auto_updater 143 | // Lets do this. :o 144 | this.autoUpdater.checkForUpdates() 145 | } 146 | 147 | /** 148 | * Install the downloaded update 149 | */ 150 | install () { 151 | // Run autoUpdaters quitAndInstall() 152 | // This will restart the app and install the new update. 153 | this.autoUpdater.quitAndInstall() 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /test/app/main.js: -------------------------------------------------------------------------------- 1 | var app = require('app') 2 | var Mocha = require('mocha') 3 | 4 | app.on('ready', function() { 5 | var mocha = new Mocha 6 | mocha.addFile('test/test.js') 7 | 8 | mocha.run(function (failures) { 9 | process.on('exit', function () { 10 | process.exit(failures) 11 | }) 12 | 13 | app.quit() 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /test/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "main": "main.js" 5 | } 6 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var GhReleases = require('../') 2 | var assert = require('assert') 3 | var semver = require('semver') 4 | 5 | describe('GhReleases', function () { 6 | this.timeout(7000) 7 | 8 | var updater = null 9 | 10 | before(function () { 11 | var options = { 12 | repo: 'jenslind/electron-gh-releases-test', 13 | currentVersion: '1.0.0' 14 | } 15 | 16 | updater = new GhReleases(options) 17 | }) 18 | 19 | describe('_getLatestTag()', function () { 20 | it('should get the latest release tag from the repo', function (done) { 21 | updater._getLatestTag() 22 | .then(function (tag) { 23 | assert(semver.valid(tag)) 24 | done() 25 | }) 26 | }) 27 | }) 28 | 29 | describe('_getCurrentVersion()', function () { 30 | it('should get the current version', function () { 31 | assert.equal(updater._getCurrentVersion(), '1.0.0') 32 | }) 33 | }) 34 | 35 | describe('_newVersion()', function () { 36 | it('should compare latest to current version', function (done) { 37 | assert(!updater._newVersion('1.0.0')) 38 | assert(updater._newVersion('2.0.0')) 39 | done() 40 | }) 41 | }) 42 | 43 | describe('_getFeedUrl()', function () { 44 | it('should make sure feed url exists', function (done) { 45 | updater._getFeedUrl('0.4.0') 46 | .then(function (feedUrl) { 47 | assert.equal(feedUrl, 'https://raw.githubusercontent.com/jenslind/electron-gh-releases-test/master/auto_updater.json') 48 | done() 49 | }) 50 | }) 51 | }) 52 | }) 53 | --------------------------------------------------------------------------------