Skip to content

Commit

Permalink
feat: NSIS Updater API to Start Downloading
Browse files Browse the repository at this point in the history
Closes #972
  • Loading branch information
develar committed Dec 23, 2016
1 parent c10531b commit eff85c3
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 46 deletions.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,7 @@ Please note that everything is packaged into an asar archive [by default](https:
* `.dmg`: macOS installer, required for the initial installation process on macOS.
* `-mac.zip`: required for Squirrel.Mac.

To benefit from auto updates, you have to implement and configure Electron's [`autoUpdater`](http://electron.atom.io/docs/latest/api/auto-updater/) module ([example](https://github.com/develar/onshape-desktop-shell/blob/master/src/AppUpdater.ts)).
You also need to deploy your releases to a server.
Consider using [Nuts](https://github.com/GitbookIO/nuts) (uses GitHub as a backend to store the assets), [Electron Release Server](https://github.com/ArekSredzki/electron-release-server) or [Squirrel Updates Server](https://github.com/Aluxian/squirrel-updates-server).
See the [Publishing Artifacts](https://github.com/electron-userland/electron-builder/wiki/Publishing-Artifacts) section of the [Wiki](https://github.com/electron-userland/electron-builder/wiki) for more information on how to configure your CI environment for automated deployments.
See the [Auto Update](https://github.com/electron-userland/electron-builder/wiki/Auto-Update) section of the [Wiki](https://github.com/electron-userland/electron-builder/wiki).

# CLI Usage
Execute `node_modules/.bin/build --help` to get the actual CLI usage guide.
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ cache:
- '%USERPROFILE%\.electron'

environment:
TEST_FILES: BuildTest,extraMetadataTest,filesTest,globTest,nsisUpdaterTest,appxTest,winPackagerTest
TEST_FILES: BuildTest,extraMetadataTest,filesTest,globTest,nsisUpdaterTest

install:
- ps: Install-Product node 6 x64
Expand Down
2 changes: 1 addition & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ dependencies:
- sudo apt-get install git-lfs=1.3.0
- ssh [email protected] git-lfs-authenticate $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME.git download
- git lfs pull
- docker run --rm --env-file ./test/docker-env.list -v ${PWD}:/project -v ~/.electron:/root/.electron -v ~/.cache/electron-builder:/root/.cache/electron-builder electronuserland/electron-builder:$([ "$CIRCLE_NODE_INDEX" == "2" ] && echo "4" || echo "wine") /bin/bash -c "node ./test/yarn.js && node ./test/yarn.js test"
- docker run --rm --env-file ./test/docker-env.list -v ${PWD}:/project -v ~/.electron:/root/.electron -v ~/.cache/electron-builder:/root/.cache/electron-builder electronuserland/electron-builder:$([ "$CIRCLE_NODE_INDEX" == "2" ] && echo "6" || echo "wine") /bin/bash -c "node ./test/yarn.js && node ./test/yarn.js test"

test:
override:
Expand Down
2 changes: 1 addition & 1 deletion docker/6/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM electronuserland/electron-builder:base

ENV NODE_VERSION 6.9.1
ENV NODE_VERSION 6.9.2

# https://github.com/npm/npm/issues/4531
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
Expand Down
2 changes: 1 addition & 1 deletion docker/7/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM electronuserland/electron-builder:base

ENV NODE_VERSION 7.2.0
ENV NODE_VERSION 7.3.0

# https://github.com/npm/npm/issues/4531
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
Expand Down
39 changes: 38 additions & 1 deletion docs/Auto Update.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
To benefit from auto updates, you have to implement and configure Electron's [`autoUpdater`](http://electron.atom.io/docs/latest/api/auto-updater/) module ([example](https://github.com/develar/onshape-desktop-shell/blob/master/src/AppUpdater.ts)).

See the [Publishing Artifacts](https://github.com/electron-userland/electron-builder/wiki/Publishing-Artifacts) section of the [Wiki](https://github.com/electron-userland/electron-builder/wiki) for more information on how to configure your CI environment for automated deployments.


**NOTICE**: [macOS auto-update](https://github.com/electron/electron/blob/master/docs/api/auto-updater.md#macos) is not yet simplified. Update providers supported only on Windows.

## Quick Setup Guide

1. Install `electron-auto-updater` as app dependency.

2. [Configure publish](https://github.com/electron-userland/electron-builder/wiki/Options#buildpublish).
Expand All @@ -19,4 +28,32 @@
}
```
Currently, `generic` (any HTTPS web server), `github` and `bintray` are supported. `latest.yml` will be generated in addition to installer for `generic` and `github` and must be uploaded also (in short: only `bintray` doesn't use `latest.yml` and this file must be not uploaded on Bintray).
Currently, `generic` (any HTTPS web server), `github` and `bintray` are supported. `latest.yml` will be generated in addition to installer for `generic` and `github` and must be uploaded also (in short: only `bintray` doesn't use `latest.yml` and this file must be not uploaded on Bintray).

## Options

Name | Default | Description
--------------------|-------------------------|------------
autoDownload | true | Automatically download an update when it is found.

## Methods

The `autoUpdater` object has the following methods:

### `autoUpdater.setFeedURL(options)`

* `options` GenericServerOptions | BintrayOptions | GithubOptions — if you want to override configuration in the `app-update.yml`.

Sets the `options`. Windows-only for now. On macOS please refer [electron setFeedURL reference](https://github.com/electron/electron/blob/master/docs/api/auto-updater.md#autoupdatersetfeedurlurl-requestheaders).

### `autoUpdater.checkForUpdates(): Promise<UpdateCheckResult>`

Asks the server whether there is an update. On macOS you must call `setFeedURL` before using this API.

### `autoUpdater.quitAndInstall()`

Restarts the app and installs the update after it has been downloaded. It
should only be called after `update-downloaded` has been emitted.

**Note:** `autoUpdater.quitAndInstall()` will close all application windows first and only emit `before-quit` event on `app` after that.
This is different from the normal quit event sequence.
72 changes: 51 additions & 21 deletions nsis-auto-updater/src/NsisUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ import * as path from "path"
import { tmpdir } from "os"
import { gt as isVersionGreaterThan, valid as parseVersion } from "semver"
import { download } from "../../src/util/httpRequest"
import { Provider, UpdateCheckResult } from "./api"
import { Provider, UpdateCheckResult, FileInfo } from "./api"
import { BintrayProvider } from "./BintrayProvider"
import BluebirdPromise from "bluebird-lst-c"
import { BintrayOptions, PublishConfiguration, GithubOptions, GenericServerOptions } from "../../src/options/publishOptions"
import { readFile } from "fs-extra-p"
import { BintrayOptions, PublishConfiguration, GithubOptions, GenericServerOptions, VersionInfo } from "../../src/options/publishOptions"
import { readFile, mkdtemp } from "fs-extra-p"
import { safeLoad } from "js-yaml"
import { GenericProvider } from "./GenericProvider"
import { GitHubProvider } from "./GitHubProvider"
import { executorHolder } from "../../src/util/httpExecutor"
import { ElectronHttpExecutor } from "./electronHttpExecutor"

export class NsisUpdater extends EventEmitter {
/**
* Automatically download an update when it is found.
*/
public autoDownload = true

private setupPath: string | null

private updateAvailable = false
Expand All @@ -29,6 +34,9 @@ export class NsisUpdater extends EventEmitter {

private quitHandlerAdded = false

private versionInfo: VersionInfo | null
private fileInfo: FileInfo | null

constructor(options?: PublishConfiguration | BintrayOptions | GithubOptions) {
super()

Expand All @@ -39,7 +47,7 @@ export class NsisUpdater extends EventEmitter {
else {
this.app = require("electron").app
executorHolder.httpExecutor = new ElectronHttpExecutor()
this.untilAppReady = new BluebirdPromise((resolve, reject) => {
this.untilAppReady = new BluebirdPromise(resolve => {
if (this.app.isReady()) {
resolve()
}
Expand All @@ -55,11 +63,12 @@ export class NsisUpdater extends EventEmitter {
}
}

//noinspection JSMethodCanBeStatic,JSUnusedGlobalSymbols
getFeedURL(): string | null | undefined {
return JSON.stringify(this.clientPromise, null, 2)
return "Deprecated. Do not use it."
}

setFeedURL(value: string | PublishConfiguration | BintrayOptions | GithubOptions | GenericServerOptions) {
setFeedURL(value: PublishConfiguration | BintrayOptions | GithubOptions | GenericServerOptions) {
this.clientPromise = BluebirdPromise.resolve(createClient(value))
}

Expand Down Expand Up @@ -103,29 +112,50 @@ export class NsisUpdater extends EventEmitter {
const fileInfo = await client.getUpdateFile(versionInfo)

this.updateAvailable = true
this.versionInfo = versionInfo
this.fileInfo = fileInfo

this.emit("update-available")

const mkdtemp: (prefix: string) => Promise<string> = require("fs-extra-p").mkdtemp
//noinspection ES6MissingAwait
return {
versionInfo: versionInfo,
fileInfo: fileInfo,
downloadPromise: mkdtemp(`${path.join(tmpdir(), "up")}-`)
.then(it => download(fileInfo.url, path.join(it, fileInfo.name), fileInfo.sha2 == null ? null : {sha2: fileInfo.sha2}))
.then(it => {
this.setupPath = it
this.addQuitHandler()
this.emit("update-downloaded", {}, null, versionInfo.version, null, null, () => {
this.quitAndInstall()
})
return it
})
.catch(e => {
this.emit("error", e, (e.stack || e).toString())
throw e
}),
downloadPromise: this.autoDownload ? this.downloadUpdate() : null,
}
}

/**
* Start downloading update manually. You can use this method if `autoDownload` option is set to `false`.
* @returns {Promise<string>} Path to downloaded file.
*/
async downloadUpdate() {
const versionInfo = this.versionInfo
const fileInfo = this.fileInfo

if (versionInfo == null || fileInfo == null) {
const message = "Please check update first"
const error = new Error(message)
this.emit("error", error, message)
throw error
}

return mkdtemp(`${path.join(tmpdir(), "up")}-`)
.then(it => download(fileInfo.url, path.join(it, fileInfo.name), fileInfo.sha2 == null ? null : {sha2: fileInfo.sha2}))
.then(it => {
this.setupPath = it
this.addQuitHandler()
this.emit("update-downloaded", {}, null, versionInfo.version, null, null, () => {
this.quitAndInstall()
})
return it
})
.catch(e => {
this.emit("error", e, (e.stack || e).toString())
throw e
})
}

private addQuitHandler() {
if (this.quitHandlerAdded) {
return
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
"scripts": {
"compile": "ts-babel . nsis-auto-updater test",
"lint": "node ./test/lint.js",
"pretest": "yarn run compile && yarn run lint",
"pretest": "node ./test/yarn.js run compile && node ./test/yarn.js run lint",
"check-deps": "node ./test/out/helpers/checkDeps.js",
"test": "node ./test/out/helpers/runTests.js",
"test-linux": "docker run --rm -ti -v ${PWD}:/project -v ${PWD##*/}-node-modules:/project/node_modules -v ~/.electron:/root/.electron electronuserland/electron-builder:wine /test.sh",
"pack-updater": "cd nsis-auto-updater && yarn --production && cd ..",
"pack-updater": "cd nsis-auto-updater && node ./test/yarn.js --production && cd ..",
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
"//": "Update wiki if docs changed. Update only if functionalily are generally available (latest release, not next)",
"update-wiki": "git subtree split -b wiki --prefix docs/ && git push -f wiki wiki:master",
Expand Down
30 changes: 30 additions & 0 deletions test/out/__snapshots__/nsisUpdaterTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ Object {
}
`;

exports[`test file url generic - manual download 1`] = `
Object {
"name": "TestApp Setup 1.1.0.exe",
"sha2": "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2",
"url": "https://develar.s3.amazonaws.com/test/TestApp Setup 1.1.0.exe",
}
`;

exports[`test file url generic - manual download 2`] = `
Array [
"checking-for-update",
"update-available",
]
`;

exports[`test file url generic 1`] = `
Object {
"name": "TestApp Setup 1.1.0.exe",
Expand All @@ -14,10 +29,25 @@ Object {
}
`;

exports[`test file url generic 2`] = `
Array [
"checking-for-update",
"update-available",
"update-downloaded",
]
`;

exports[`test file url github 1`] = `
Object {
"name": "TestApp-Setup-1.1.0.exe",
"sha2": "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2",
"url": "https://github.com/develar/__test_nsis_release/releases/download/v1.1.0/TestApp-Setup-1.1.0.exe",
}
`;

exports[`test test error 1`] = `
Array [
"checking-for-update",
"error",
]
`;
47 changes: 33 additions & 14 deletions test/src/nsisUpdaterTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,34 @@ test("file url generic", async () => {
g.__test_resourcesPath = testResourcesPath
const updater: NsisUpdater = new NsisUpdaterClass()

const actualEvents: Array<string> = []
const expectedEvents = ["checking-for-update", "update-available", "update-downloaded"]
for (const eventName of expectedEvents) {
updater.addListener(eventName, () => {
actualEvents.push(eventName)
})
}
const actualEvents = trackEvents(updater)

const updateCheckResult = await updater.checkForUpdates()
expect(updateCheckResult.fileInfo).toMatchSnapshot()
await assertThat(path.join(await updateCheckResult.downloadPromise)).isFile()

expect(actualEvents).toEqual(expectedEvents)
expect(actualEvents).toMatchSnapshot()
})

test("file url generic - manual download", async () => {
const tmpDir = new TmpDir()
const testResourcesPath = await tmpDir.getTempFile("update-config")
await outputFile(path.join(testResourcesPath, "app-update.yml"), safeDump(<GenericServerOptions>{
provider: "generic",
url: "https://develar.s3.amazonaws.com/test",
}))
g.__test_resourcesPath = testResourcesPath
const updater: NsisUpdater = new NsisUpdaterClass()
updater.autoDownload = false

const actualEvents = trackEvents(updater)

const updateCheckResult = await updater.checkForUpdates()
expect(updateCheckResult.fileInfo).toMatchSnapshot()
expect(updateCheckResult.downloadPromise).toBeNull()
expect(actualEvents).toMatchSnapshot()

await assertThat(path.join(await updater.downloadUpdate())).isFile()
})

test("file url github", async () => {
Expand Down Expand Up @@ -126,14 +141,18 @@ test("test error", async () => {
g.__test_resourcesPath = null
const updater: NsisUpdater = new NsisUpdaterClass()

const actualEvents = trackEvents(updater)

await assertThat(updater.checkForUpdates()).throws("Path must be a string. Received undefined")
expect(actualEvents).toMatchSnapshot()
})

function trackEvents(updater: NsisUpdater) {
const actualEvents: Array<string> = []
const expectedEvents = ["checking-for-update", "error", "error"]
for (const eventName of expectedEvents) {
for (const eventName of ["checking-for-update", "update-available", "update-downloaded", "error"]) {
updater.addListener(eventName, () => {
actualEvents.push(eventName)
})
}

await assertThat(updater.checkForUpdates()).throws("Path must be a string. Received undefined")
expect(actualEvents).toEqual(expectedEvents)
})
return actualEvents
}

0 comments on commit eff85c3

Please sign in to comment.