From f275831ddda3911ed03eaf1c260e83fe17305d3f Mon Sep 17 00:00:00 2001 From: develar Date: Sun, 16 Apr 2017 20:18:13 +0200 Subject: [PATCH] feat(electron-updater): GitHub: Allow pre-release builds to be auto updated Close #1391 --- package.json | 1 + packages/electron-updater/package.json | 3 +- packages/electron-updater/src/AppUpdater.ts | 64 +++++++++---------- .../electron-updater/src/GitHubProvider.ts | 45 +++++++++++-- .../out/__snapshots__/nsisUpdaterTest.js.snap | 18 ++++++ test/src/nsisUpdaterTest.ts | 38 ++++++++++- yarn.lock | 6 ++ 7 files changed, 136 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 502fd154bd3..d65c52a6626 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "tunnel-agent": "^0.6.0", "update-notifier": "^2.1.0", "uuid-1345": "^0.99.6", + "xelement": "^1.0.13", "yargs": "^7.1.0" }, "devDependencies": { diff --git a/packages/electron-updater/package.json b/packages/electron-updater/package.json index 32dce3342b9..439dd1ed171 100644 --- a/packages/electron-updater/package.json +++ b/packages/electron-updater/package.json @@ -18,7 +18,8 @@ "semver": "^5.3.0", "source-map-support": "^0.4.14", "electron-builder-http": "~0.0.0-semantic-release", - "electron-is-dev": "^0.1.2" + "electron-is-dev": "^0.1.2", + "xelement": "^1.0.13" }, "typings": "./out/electron-updater.d.ts" } diff --git a/packages/electron-updater/src/AppUpdater.ts b/packages/electron-updater/src/AppUpdater.ts index 4ada45eed5e..8fc7d5d01fe 100644 --- a/packages/electron-updater/src/AppUpdater.ts +++ b/packages/electron-updater/src/AppUpdater.ts @@ -142,7 +142,7 @@ export abstract class AppUpdater extends EventEmitter { client = new GenericProvider({provider: "generic", url: options}) } else { - client = createClient(options) + client = this.createClient(options) } this.clientPromise = BluebirdPromise.resolve(client) } @@ -184,7 +184,7 @@ export abstract class AppUpdater extends EventEmitter { private async doCheckForUpdates(): Promise { if (this.clientPromise == null) { - this.clientPromise = this.loadUpdateConfig().then(it => createClient(it)) + this.clientPromise = this.loadUpdateConfig().then(it => this.createClient(it)) } const client = await this.clientPromise @@ -288,41 +288,41 @@ export abstract class AppUpdater extends EventEmitter { } return requestHeaders } -} -function createClient(data: string | PublishConfiguration) { - if (typeof data === "string") { - throw new Error("Please pass PublishConfiguration object") - } + private createClient(data: string | PublishConfiguration) { + if (typeof data === "string") { + throw new Error("Please pass PublishConfiguration object") + } - const provider = (data).provider - switch (provider) { - case "github": - const githubOptions = data - const token = (githubOptions.private ? process.env.GH_TOKEN : null) || githubOptions.token - if (token == null) { - return new GitHubProvider(githubOptions) - } - else { - return new PrivateGitHubProvider(githubOptions, token) + const provider = (data).provider + switch (provider) { + case "github": + const githubOptions = data + const token = (githubOptions.private ? process.env.GH_TOKEN : null) || githubOptions.token + if (token == null) { + return new GitHubProvider(githubOptions, this) + } + else { + return new PrivateGitHubProvider(githubOptions, token) + } + + case "s3": { + const s3 = data + return new GenericProvider({ + provider: "generic", + url: s3Url(s3), + channel: s3.channel || "" + }) } - - case "s3": { - const s3 = data - return new GenericProvider({ - provider: "generic", - url: s3Url(s3), - channel: s3.channel || "" - }) - } - case "generic": - return new GenericProvider(data) + case "generic": + return new GenericProvider(data) - case "bintray": - return new BintrayProvider(data) + case "bintray": + return new BintrayProvider(data) - default: - throw new Error(`Unsupported provider: ${provider}`) + default: + throw new Error(`Unsupported provider: ${provider}`) + } } } \ No newline at end of file diff --git a/packages/electron-updater/src/GitHubProvider.ts b/packages/electron-updater/src/GitHubProvider.ts index df48cb0217a..31b3f9045e7 100644 --- a/packages/electron-updater/src/GitHubProvider.ts +++ b/packages/electron-updater/src/GitHubProvider.ts @@ -5,6 +5,7 @@ import { RequestOptions } from "http" import * as path from "path" import { parse as parseUrl } from "url" import { FileInfo, formatUrl, getChannelFilename, getCurrentPlatform, getDefaultChannelName, Provider } from "./api" +import { AppUpdater } from "./AppUpdater" import { validateUpdateInfo } from "./GenericProvider" export abstract class BaseGitHubProvider extends Provider { @@ -24,14 +25,39 @@ export abstract class BaseGitHubProvider extends Provider< } export class GitHubProvider extends BaseGitHubProvider { - constructor(protected readonly options: GithubOptions) { + constructor(protected readonly options: GithubOptions, private readonly updater: AppUpdater) { super(options, "github.com") } async getLatestVersion(): Promise { const basePath = this.basePath const cancellationToken = new CancellationToken() - const version = await this.getLatestVersionString(basePath, cancellationToken) + + const xElement = require("xelement") + const feedXml = await request(Object.assign({ + path: `${basePath}.atom`, + headers: Object.assign({}, this.requestHeaders, {Accept: "application/xml"}) + }, this.baseUrl), cancellationToken) + + const feed = new xElement.Parse(feedXml) + const latestRelease = feed.element("entry") + if (latestRelease == null) { + throw new Error(`No published versions on GitHub`) + } + + let version: string + try { + if (this.updater.allowPrerelease) { + version = latestRelease.element("link").getAttr("href").match(/\/tag\/v?([^\/]+)$/)[1] + } + else { + version = await this.getLatestVersionString(basePath, cancellationToken) + } + } + catch (e) { + throw new Error(`Cannot parse releases feed: ${e.stack || e.message},\nXML:\n${feedXml}`) + } + let result: any const channelFile = getChannelFilename(getDefaultChannelName()) const requestOptions = Object.assign({path: this.getBaseDownloadPath(version, channelFile), headers: this.requestHeaders || undefined}, this.baseUrl) @@ -39,8 +65,10 @@ export class GitHubProvider extends BaseGitHubProvider { result = await request(requestOptions, cancellationToken) } catch (e) { - if (e instanceof HttpError && e.response.statusCode === 404) { - throw new Error(`Cannot find ${channelFile} in the latest release artifacts (${formatUrl(requestOptions)}): ${e.stack || e.message}`) + if (!this.updater.allowPrerelease) { + if (e instanceof HttpError && e.response.statusCode === 404) { + throw new Error(`Cannot find ${channelFile} in the latest release artifacts (${formatUrl(requestOptions)}): ${e.stack || e.message}`) + } } throw e } @@ -49,13 +77,20 @@ export class GitHubProvider extends BaseGitHubProvider { if (getCurrentPlatform() === "darwin") { result.releaseJsonUrl = `${githubUrl(this.options)}/${requestOptions.path}` } + + if (result.releaseName == null) { + result.releaseName = latestRelease.getElementValue("title") + } + if (result.releaseNotes == null) { + result.releaseNotes = latestRelease.getElementValue("content") + } return result } private async getLatestVersionString(basePath: string, cancellationToken: CancellationToken): Promise { const requestOptions: RequestOptions = Object.assign({ path: `${basePath}/latest`, - headers: Object.assign({Accept: "application/json"}, this.requestHeaders) + headers: Object.assign({}, this.requestHeaders, {Accept: "application/json"}) }, this.baseUrl) try { // do not use API to avoid limit diff --git a/test/out/__snapshots__/nsisUpdaterTest.js.snap b/test/out/__snapshots__/nsisUpdaterTest.js.snap index c80c04472f6..23fc067edde 100644 --- a/test/out/__snapshots__/nsisUpdaterTest.js.snap +++ b/test/out/__snapshots__/nsisUpdaterTest.js.snap @@ -77,6 +77,24 @@ Object { } `; +exports[`file url github pre-release 1`] = ` +Object { + "name": "TestApp-Setup-1.1.0.exe", + "sha2": "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2", + "url": "https://github.com/develar/__test_nsis_release/releases/download/v1.5.0/TestApp-Setup-1.1.0.exe", +} +`; + +exports[`file url github pre-release 2`] = ` +Object { + "path": "TestApp Setup 1.1.0.exe", + "releaseName": "1.5.0", + "releaseNotes": "

Test pre-release. qefr

", + "sha2": "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2", + "version": "1.5.0", +} +`; + exports[`file url github private 1`] = ` Object { "headers": Object { diff --git a/test/src/nsisUpdaterTest.ts b/test/src/nsisUpdaterTest.ts index 76ab027bb41..78659723f8a 100644 --- a/test/src/nsisUpdaterTest.ts +++ b/test/src/nsisUpdaterTest.ts @@ -191,7 +191,7 @@ test("file url github", async () => { updater.updateConfigPath = await writeUpdateConfig({ provider: "github", owner: "develar", - repo: "__test_nsis_release", + repo: "__test_nsis_release", }) const actualEvents: Array = [] @@ -209,6 +209,42 @@ test("file url github", async () => { expect(actualEvents).toEqual(expectedEvents) }) +test("file url github pre-release", async () => { + const updater = new NsisUpdater(null, { + getVersion: function () { + return "1.6.0-beta.1" + }, + + getAppPath: function () { + }, + + on: function () { + // ignored + }, + }) + updater.updateConfigPath = await writeUpdateConfig({ + provider: "github", + owner: "develar", + repo: "__test_nsis_release", + }) + + const actualEvents: Array = [] + const expectedEvents = ["checking-for-update", "update-available", "update-downloaded"] + for (const eventName of expectedEvents) { + updater.addListener(eventName, () => { + actualEvents.push(eventName) + }) + } + + const updateCheckResult = await updater.checkForUpdates() + expect(updateCheckResult.fileInfo).toMatchSnapshot() + expect(updateCheckResult.versionInfo).toMatchSnapshot() + + await assertThat(path.join(await updateCheckResult.downloadPromise)).isFile() + + expect(actualEvents).toEqual(expectedEvents) +}) + test.skip("file url github private", async () => { const updater = new NsisUpdater() updater.updateConfigPath = await writeUpdateConfig({ diff --git a/yarn.lock b/yarn.lock index 39cb5bfb2f1..403c7dd434b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3481,6 +3481,12 @@ xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" +xelement@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/xelement/-/xelement-1.0.13.tgz#36b724d1e6489d6aae285b06995ae5e33cec10b7" + dependencies: + sax "^1.2.1" + xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"