diff --git a/docs/MAINTAINERS.md b/docs/MAINTAINERS.md index 6a477ad0..229fa016 100644 --- a/docs/MAINTAINERS.md +++ b/docs/MAINTAINERS.md @@ -18,6 +18,8 @@ Preparing a new version - Re-take `screenshot.png` so it displays the latest version in the bottom right corner. +- Revise the `updates.semverRange` version in `package.json` + - Commit the changes with the version number as the commit title, including the `v` prefix, to `master`. For example: diff --git a/docs/PUBLISHING.md b/docs/PUBLISHING.md index a35454b0..2cd05443 100644 --- a/docs/PUBLISHING.md +++ b/docs/PUBLISHING.md @@ -4,6 +4,48 @@ Publishing Etcher This is a small guide to package and publish Etcher to all supported operating systems. +Release Types +------------- + +Etcher supports **production** and **snapshot** release types. Each is +published to a different S3 bucket, and production release types are code +signed, while snapshot release types aren't and include a short git commit-hash +as a build number. For example, `1.0.0-beta.19` is a production release type, +while `1.0.0-beta.19+531ab82` is a snapshot release type. + +In terms of comparison: `1.0.0-beta.19` (production) < `1.0.0-beta.19+531ab82` +(snapshot) < `1.0.0-rc.1` (production) < `1.0.0-rc.1+7fde24a` (snapshot) < +`1.0.0` (production) < `1.0.0+2201e5f` (snapshot). Keep in mind that if you're +running a production release type, you'll only be prompted to update to +production release types, and if you're running a snapshot release type, you'll +only be prompted to update to other snapshot release types. + +The build system creates (and publishes) snapshot release types by default, but +you can build a specific release type by setting the `RELEASE_TYPE` make +variable. For example: + +```sh +make RELEASE_TYPE=snapshot +make RELEASE_TYPE=production +``` + +We can control the version range a specific Etcher version will consider when +showing the update notification dialog by tweaking the `updates.semverRange` +property of `package.json`. + +Update Channels +--------------- + +Etcher has a setting to include the unstable update channel. If this option is +set, Etcher will consider both stable and unstable versions when showing the +update notifier dialog. Unstable versions are the ones that contain a `beta` +pre-release tag. For example: + +- Production unstable version: `1.4.0-beta.1` +- Snapshot unstable version: `1.4.0-beta.1+7fde24a` +- Production stable version: `1.4.0` +- Snapshot stable version: `1.4.0+7fde24a` + Signing ------- @@ -29,40 +71,31 @@ employee by asking for it from the relevant people. Packaging --------- +The resulting installers will be saved to `release/out`. + +Run the following commands: ### OS X -Run the following command: - ```sh make electron-installer-dmg make electron-installer-app-zip ``` -The resulting installers will be saved to `release/out`. - ### GNU/Linux -Run the following command: - ```sh make electron-installer-appimage make electron-installer-debian ``` -The resulting installers will be saved to `release/out`. - ### Windows -Run the following command: - ```sh make electron-installer-zip make electron-installer-nsis ``` -The resulting installers will be saved to `etcher-release/installers`. - Publishing to Bintray --------------------- @@ -76,7 +109,7 @@ Make sure you set the following environment variables: Run the following command: ```sh -make publish-bintray-debian RELEASE_TYPE= +make publish-bintray-debian ``` Publishing to S3 @@ -91,7 +124,7 @@ Run the following command to publish all files for the current combination of _platform_ and _arch_ (building them if necessary): ```sh -make publish-aws-s3 RELEASE_TYPE= +make publish-aws-s3 ``` Also add links to each AWS S3 file in [GitHub Releases][github-releases]. See diff --git a/docs/USER-DOCUMENTATION.md b/docs/USER-DOCUMENTATION.md index 6bf9b1f7..1dc46964 100644 --- a/docs/USER-DOCUMENTATION.md +++ b/docs/USER-DOCUMENTATION.md @@ -149,6 +149,21 @@ In Windows: set ETCHER_DISABLE_UPDATES=1 ``` +Simulate an update alert +------------------------ + +You can set the `ETCHER_FAKE_S3_LATEST_VERSION` environment variable to a valid +semver version (greater than the current version) to trick the application into +thinking that what you put there is the latest available version, therefore +causing the update notification dialog to be presented at startup. + +Note that the value of the variable will be ignored if it doesn't match the +release type of the current application version. For example, setting the +variable to a production version (e.g. `ETCHER_FAKE_S3_LATEST_VERSION=2.0.0`) +will be ignored if you're running a snapshot build, and vice-versa. + +See [`PUBLISHING.md`][publishing] for more details about release types. + Recovering broken drives ------------------------ @@ -223,6 +238,7 @@ platforms. [electron]: http://electron.atom.io [electron-supported-platforms]: https://github.com/electron/electron/blob/master/docs/tutorial/supported-platforms.md [etcher-cli]: https://github.com/resin-io/etcher/blob/master/docs/CLI.md +[publishing]: https://github.com/resin-io/etcher/blob/master/docs/PUBLISHING.md [windows-usb-tool]: https://www.microsoft.com/en-us/download/windows-usb-dvd-download-tool [rufus]: https://rufus.akeo.ie [unetbootin]: https://unetbootin.github.io diff --git a/lib/gui/app.js b/lib/gui/app.js index ad3b1471..ace089ca 100644 --- a/lib/gui/app.js +++ b/lib/gui/app.js @@ -28,10 +28,15 @@ var angular = require('angular'); const electron = require('electron'); const Bluebird = require('bluebird'); +const semver = require('semver'); +const _ = require('lodash'); const EXIT_CODES = require('../shared/exit-codes'); const messages = require('../shared/messages'); +const s3Packages = require('../shared/s3-packages'); +const release = require('../shared/release'); const packageJSON = require('../../package.json'); const flashState = require('./models/flash-state'); +const settings = require('./models/settings'); const windowProgress = require('./os/window-progress'); const Store = require('./models/store'); @@ -86,23 +91,40 @@ app.run(() => { app.run((AnalyticsService, ErrorService, UpdateNotifierService, SelectionStateModel) => { AnalyticsService.logEvent('Application start'); - const shouldCheckForUpdates = UpdateNotifierService.shouldCheckForUpdates(); + const currentVersion = packageJSON.version; + const currentReleaseType = release.getReleaseType(currentVersion); + const shouldCheckForUpdates = UpdateNotifierService.shouldCheckForUpdates({ + ignoreSleepUpdateCheck: currentReleaseType !== release.RELEASE_TYPE.PRODUCTION + }); - if (!shouldCheckForUpdates || process.env.ETCHER_DISABLE_UPDATES) { + if (_.some([ + !shouldCheckForUpdates, + process.env.ETCHER_DISABLE_UPDATES, + currentReleaseType === release.RELEASE_TYPE.UNKNOWN + ])) { AnalyticsService.logEvent('Not checking for updates', { shouldCheckForUpdates, - disableUpdatesEnvironmentVariable: process.env.ETCHER_DISABLE_UPDATES + disableUpdatesEnvironmentVariable: process.env.ETCHER_DISABLE_UPDATES, + releaseType: currentReleaseType }); return; } + const updateSemverRange = packageJSON.updates.semverRange; + const includeUnstableChannel = settings.get('includeUnstableUpdateChannel'); + AnalyticsService.logEvent('Checking for updates', { - currentVersion: packageJSON.version + currentVersion, + releaseType: currentReleaseType, + updateSemverRange, + includeUnstableChannel }); - UpdateNotifierService.isLatestVersion().then((isLatestVersion) => { - - if (isLatestVersion) { + s3Packages.getLatestVersion(currentReleaseType, { + range: updateSemverRange, + includeUnstableChannel + }).then((latestVersion) => { + if (semver.gte(currentVersion, latestVersion || '0.0.0')) { AnalyticsService.logEvent('Update notification skipped', { reason: 'Latest version' }); @@ -121,12 +143,14 @@ app.run((AnalyticsService, ErrorService, UpdateNotifierService, SelectionStateMo return Bluebird.resolve(); } - AnalyticsService.logEvent('Notifying update'); - - return UpdateNotifierService.notify(); + AnalyticsService.logEvent('Notifying update', { + latestVersion + }); + return UpdateNotifierService.notify(latestVersion, { + allowSleepUpdateCheck: currentReleaseType === release.RELEASE_TYPE.PRODUCTION + }); }).catch(ErrorService.reportException); - }); app.run((AnalyticsService) => { diff --git a/lib/gui/components/update-notifier/services/update-notifier.js b/lib/gui/components/update-notifier/services/update-notifier.js index e7a5ea62..8958c786 100644 --- a/lib/gui/components/update-notifier/services/update-notifier.js +++ b/lib/gui/components/update-notifier/services/update-notifier.js @@ -17,104 +17,35 @@ 'use strict'; const _ = require('lodash'); -const semver = require('semver'); -const etcherLatestVersion = require('etcher-latest-version'); const units = require('../../../../shared/units'); const settings = require('../../../models/settings'); -module.exports = function($http, $q, ModalService, UPDATE_NOTIFIER_SLEEP_DAYS, ManifestBindService) { +module.exports = function(ModalService, UPDATE_NOTIFIER_SLEEP_DAYS) { /** - * @summary The current application version - * @constant - * @private - * @type {String} - */ - const CURRENT_VERSION = ManifestBindService.get('version'); - - /** - * @summary Get the latest available Etcher version - * @function - * @private - * @description - * We assume the received latest version number will not increase - * while Etcher is running and memoize it - * - * @fulfil {String} - latest version - * @returns {Promise} - * - * @example - * UpdateNotifierService.getLatestVersion().then((latestVersion) => { - * console.log(`The latest version is: ${latestVersion}`); - * }); - */ - this.getLatestVersion = _.memoize(() => { - return $q((resolve, reject) => { - return etcherLatestVersion((url, callback) => { - return $http.get(url).then((response) => { - return callback(null, response.data); - }).catch((error) => { - return callback(error); - }); - }, (error, latestVersion) => { - if (error) { - - // The error status equals this number if the request - // couldn't be made successfully, for example, because - // of a timeout on an unstable network connection. - const ERROR_CODE_UNSUCCESSFUL_REQUEST = -1; - - if (error.status === ERROR_CODE_UNSUCCESSFUL_REQUEST) { - return resolve(CURRENT_VERSION); - } - - return reject(error); - } - - return resolve(latestVersion); - }); - }); - - // Arbitrary identifier for the memoization function - }, _.constant('latest-version')); - - /** - * @summary Check if the current version is the latest version - * @function - * @public - * - * @fulfil {Boolean} - is latest version - * @returns {Promise} - * - * @example - * UpdateNotifierService.isLatestVersion().then((isLatestVersion) => { - * if (!isLatestVersion) { - * console.log('There is an update available'); - * } - * }); - */ - this.isLatestVersion = () => { - return this.getLatestVersion().then((version) => { - return semver.gte(CURRENT_VERSION, version); - }); - }; - - /** - * @summary Determine if its time to check for updates + * @summary Determine if it's time to check for updates * @function * @public * + * @param {Object} [options] - options + * @param {Boolean} [options.ignoreSleepUpdateCheck] - ignore sleep update check * @returns {Boolean} should check for updates * * @example - * if (UpdateNotifierService.shouldCheckForUpdates()) { + * if (UpdateNotifierService.shouldCheckForUpdates({ + * ignoreSleepUpdateCheck: false + * })) { * console.log('We should check for updates!'); * } */ - this.shouldCheckForUpdates = () => { + this.shouldCheckForUpdates = (options = {}) => { const lastUpdateNotify = settings.get('lastUpdateNotify'); - if (!settings.get('sleepUpdateCheck') || !lastUpdateNotify) { + if (_.some([ + !settings.get('sleepUpdateCheck'), + !lastUpdateNotify, + _.get(options, [ 'ignoreSleepUpdateCheck' ], false) + ])) { return true; } @@ -131,24 +62,28 @@ module.exports = function($http, $q, ModalService, UPDATE_NOTIFIER_SLEEP_DAYS, M * @function * @public * + * @param {String} version - version + * @param {Object} [options] - options + * @param {Boolean} [options.allowSleepUpdateCheck=true] - allow sleeping the update check * @returns {Promise} * * @example - * UpdateNotifierService.notify(); + * UpdateNotifierService.notify('1.0.0-beta.16', { + * allowSleepUpdateCheck: true + * }); */ - this.notify = () => { - return this.getLatestVersion().then((version) => { - return ModalService.open({ - template: './components/update-notifier/templates/update-notifier-modal.tpl.html', - controller: 'UpdateNotifierController as modal', - size: 'update-notifier', - resolve: { - options: _.constant({ - version - }) - } - }).result; - }); + this.notify = (version, options = {}) => { + return ModalService.open({ + template: './components/update-notifier/templates/update-notifier-modal.tpl.html', + controller: 'UpdateNotifierController as modal', + size: 'update-notifier', + resolve: { + options: _.constant({ + version, + allowSleepUpdateCheck: _.get(options, [ 'allowSleepUpdateCheck' ], true) + }) + } + }).result; }; }; diff --git a/lib/gui/components/update-notifier/templates/update-notifier-modal.tpl.html b/lib/gui/components/update-notifier/templates/update-notifier-modal.tpl.html index d3f58784..82d81775 100644 --- a/lib/gui/components/update-notifier/templates/update-notifier-modal.tpl.html +++ b/lib/gui/components/update-notifier/templates/update-notifier-modal.tpl.html @@ -6,7 +6,7 @@