diff --git a/appveyor.yml b/appveyor.yml index 1b6cc2c0a42..6cb34a669c7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,7 @@ platform: cache: - node_modules - - '%USERPROFILE%\.electron' + - '%LOCALAPPDATA%\electron\Cache' environment: TEST_FILES: ExtraBuildTest,BuildTest,extraMetadataTest,filesTest,globTest,nsisUpdaterTest,oneClickInstallerTest,installerTest diff --git a/circle.yml b/circle.yml index 2b2ecf49e77..a10dc64ff3c 100644 --- a/circle.yml +++ b/circle.yml @@ -4,7 +4,7 @@ machine: dependencies: cache_directories: - - "~/.electron" + - "~/.cache/.electron" - "~/.cache/electron-builder" # https://discuss.circleci.com/t/installing-git-lfs/867 diff --git a/package.json b/package.json index bdcd49bc54a..502fd154bd3 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "yargs": "^7.1.0" }, "devDependencies": { - "@develar/typescript-json-schema": "0.11.0", "@types/electron": "^1.4.35", "@types/ini": "^1.3.29", "@types/jest": "^19.2.2", @@ -81,6 +80,7 @@ "convert-source-map": "^1.5.0", "decompress-zip": "^0.3.0", "depcheck": "^0.6.7", + "develar-typescript-json-schema": "0.11.0", "globby": "^6.1.0", "jest-cli": "^19.0.2", "jest-environment-node-debug": "^2.0.0", diff --git a/packages/electron-updater/src/AppUpdater.ts b/packages/electron-updater/src/AppUpdater.ts index 183e3bae3ba..4ada45eed5e 100644 --- a/packages/electron-updater/src/AppUpdater.ts +++ b/packages/electron-updater/src/AppUpdater.ts @@ -6,7 +6,7 @@ import { EventEmitter } from "events" import { readFile } from "fs-extra-p" import { safeLoad } from "js-yaml" import * as path from "path" -import { gt as isVersionGreaterThan, valid as parseVersion } from "semver" +import { eq as isVersionsEqual, gt as isVersionGreaterThan, prerelease as getVersionPreleaseComponents, valid as parseVersion } from "semver" import "source-map-support/register" import { FileInfo, Provider, UpdateCheckResult, UpdaterSignal } from "./api" import { BintrayProvider } from "./BintrayProvider" @@ -25,10 +25,23 @@ export interface Logger { export abstract class AppUpdater extends EventEmitter { /** - * Automatically download an update when it is found. + * Whether to automatically download an update when it is found. */ autoDownload = true + /** + * *GitHub provider only.* Whether to allow update to pre-release versions. Defaults to `true` if application version contains prerelease components (e.g. `0.12.1-alpha.1`, here `alpha` is a prerelease component), otherwise `false`. + * + * If `true`, downgrade will be allowed (`allowDowngrade` will be set to `true`). + */ + allowPrerelease = false + + /** + * Whether to allow version downgrade (when a user from the beta channel wants to go back to the stable channel). + * Defaults to `true` if application version contains prerelease components (e.g. `0.12.1-alpha.1`, here `alpha` is a prerelease component), otherwise `false`. + */ + allowDowngrade = false + /** * The request headers. */ @@ -64,7 +77,9 @@ export abstract class AppUpdater extends EventEmitter { protected versionInfo: VersionInfo | null private fileInfo: FileInfo | null - constructor(options: PublishConfiguration | null | undefined) { + private currentVersion: string + + constructor(options: PublishConfiguration | null | undefined, app?: any) { super() this.on("error", (error: Error) => { @@ -73,8 +88,8 @@ export abstract class AppUpdater extends EventEmitter { } }) - if ((global).__test_app != null) { - this.app = (global).__test_app + if (app != null || (global).__test_app != null) { + this.app = app || (global).__test_app this.untilAppReady = BluebirdPromise.resolve() } else { @@ -96,6 +111,16 @@ export abstract class AppUpdater extends EventEmitter { }) } + const currentVersionString = this.app.getVersion() + this.currentVersion = parseVersion(currentVersionString) + if (this.currentVersion == null) { + throw new Error(`App version is not valid semver version: "${currentVersionString}`) + } + + const versionPrereleaseComponent = getVersionPreleaseComponents(this.currentVersion) + this.allowDowngrade = versionPrereleaseComponent != null && versionPrereleaseComponent.length > 0 + this.allowPrerelease = this.allowDowngrade + if (options != null) { this.setFeedURL(options) } @@ -171,16 +196,10 @@ export abstract class AppUpdater extends EventEmitter { throw new Error(`Latest version (from update server) is not valid semver version: "${latestVersion}`) } - const currentVersionString = this.app.getVersion() - const currentVersion = parseVersion(currentVersionString) - if (currentVersion == null) { - throw new Error(`App version is not valid semver version: "${currentVersion}`) - } - - if (!isVersionGreaterThan(latestVersion, currentVersion)) { + if (this.allowDowngrade ? isVersionsEqual(latestVersion, this.currentVersion) : !isVersionGreaterThan(latestVersion, this.currentVersion)) { this.updateAvailable = false if (this.logger != null) { - this.logger.info(`Update for version ${currentVersionString} is not available (latest version: ${versionInfo.version})`) + this.logger.info(`Update for version ${this.currentVersion} is not available (latest version: ${versionInfo.version}, downgrade is ${this.allowDowngrade ? "allowed" : "disallowed"}.`) } this.emit("update-not-available", versionInfo) return { diff --git a/packages/electron-updater/src/NsisUpdater.ts b/packages/electron-updater/src/NsisUpdater.ts index 7026611095a..6c77bb3cc6d 100644 --- a/packages/electron-updater/src/NsisUpdater.ts +++ b/packages/electron-updater/src/NsisUpdater.ts @@ -14,8 +14,8 @@ export class NsisUpdater extends AppUpdater { private quitAndInstallCalled = false private quitHandlerAdded = false - constructor(options?: PublishConfiguration) { - super(options) + constructor(options?: PublishConfiguration, app?: any) { + super(options, app) } /** diff --git a/test/out/__snapshots__/nsisUpdaterTest.js.snap b/test/out/__snapshots__/nsisUpdaterTest.js.snap index 18e47ca90d9..c80c04472f6 100644 --- a/test/out/__snapshots__/nsisUpdaterTest.js.snap +++ b/test/out/__snapshots__/nsisUpdaterTest.js.snap @@ -20,6 +20,16 @@ Array [ ] `; +exports[`downgrade (allowed) 1`] = ` +Object { + "name": "TestApp Setup 1.1.0.exe", + "sha2": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "url": "https://dl.bintray.com/actperepo/generic/TestApp Setup 1.1.0.exe", +} +`; + +exports[`downgrade (disallowed) 1`] = `undefined`; + exports[`file url 1`] = ` Object { "name": "TestApp Setup 1.1.0.exe", diff --git a/test/src/nsisUpdaterTest.ts b/test/src/nsisUpdaterTest.ts index e2bd0cc2c34..76ab027bb41 100644 --- a/test/src/nsisUpdaterTest.ts +++ b/test/src/nsisUpdaterTest.ts @@ -41,18 +41,8 @@ test("check updates - no versions at all", async () => { await assertThat(updater.checkForUpdates()).throws() }) -// test("cannot find suitable file for version", async () => { -// const updater = new NsisUpdater({ -// provider: "bintray", -// owner: "actperepo", -// package: "incorrect-file-version", -// }) -// -// await assertThat(updater.checkForUpdates()).throws() -// }) - -test("file url", async () => { - const updater = new NsisUpdater() +async function testUpdateFromBintray(app: any) { + const updater = new NsisUpdater(null, app) updater.updateConfigPath = await writeUpdateConfig({ provider: "bintray", owner: "actperepo", @@ -71,9 +61,58 @@ test("file url", async () => { expect(updateCheckResult.fileInfo).toMatchSnapshot() await assertThat(path.join(await updateCheckResult.downloadPromise)).isFile() + expect(actualEvents).toEqual(expectedEvents) +} +test("file url", () => testUpdateFromBintray(null)) + +test("downgrade (disallowed)", async () => { + const updater = new NsisUpdater(null, { + getVersion: function () { + return "2.0.0" + }, + + getAppPath: function () { + }, + + on: function () { + // ignored + }, + } + ) + updater.updateConfigPath = await writeUpdateConfig({ + provider: "bintray", + owner: "actperepo", + package: "TestApp", + }) + + const actualEvents: Array = [] + const expectedEvents = ["checking-for-update", "update-not-available"] + for (const eventName of expectedEvents) { + updater.addListener(eventName, () => { + actualEvents.push(eventName) + }) + } + + const updateCheckResult = await updater.checkForUpdates() + expect(updateCheckResult.fileInfo).toMatchSnapshot() + expect(updateCheckResult.downloadPromise).toBeUndefined() + expect(actualEvents).toEqual(expectedEvents) }) +test("downgrade (allowed)", () => testUpdateFromBintray({ + getVersion: function () { + return "2.0.0-beta.1" + }, + + getAppPath: function () { + }, + + on: function () { + // ignored + }, +})) + test("file url generic", async () => { const updater = new NsisUpdater() updater.updateConfigPath = await writeUpdateConfig({ diff --git a/yarn.lock b/yarn.lock index 3e601e2e6db..39cb5bfb2f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,15 +22,6 @@ "7zip-bin-mac" "^1.0.1" "7zip-bin-win" "^2.0.2" -"@develar/typescript-json-schema@0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@develar/typescript-json-schema/-/typescript-json-schema-0.11.0.tgz#a1ac659a9b1cecdfed0e7e972d0404d7978fd8d7" - dependencies: - glob "~7.1.1" - json-stable-stringify "^1.0.1" - typescript "~2.1.5" - yargs "^7.0.2" - "@types/electron@^1.4.35": version "1.4.35" resolved "https://registry.yarnpkg.com/@types/electron/-/electron-1.4.35.tgz#48e5e6ef0b49f27b9f78b87d80f796e3f0a4f33c" @@ -1020,6 +1011,15 @@ detect-newline@^1.0.3: get-stdin "^4.0.1" minimist "^1.1.0" +develar-typescript-json-schema@0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/develar-typescript-json-schema/-/develar-typescript-json-schema-0.11.0.tgz#d73580cae2fb0b8f9a7cee3d10aff6caae751cca" + dependencies: + glob "~7.1.1" + json-stable-stringify "^1.0.1" + typescript "~2.1.5" + yargs "^7.0.2" + diff@^3.0.0, diff@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"