Skip to content

Commit

Permalink
feat(electron-updater): add error codes
Browse files Browse the repository at this point in the history
Close #2415
  • Loading branch information
develar committed Jan 2, 2018
1 parent fd70412 commit 2822049
Show file tree
Hide file tree
Showing 17 changed files with 66 additions and 54 deletions.
5 changes: 3 additions & 2 deletions packages/builder-util-runtime/src/httpExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Socket } from "net"
import { Transform } from "stream"
import { parse as parseUrl, URL } from "url"
import { CancellationToken } from "./CancellationToken"
import { newError } from "./index"
import { ProgressCallbackTransform, ProgressInfo } from "./ProgressCallbackTransform"

const debug = _debug("electron-builder")
Expand Down Expand Up @@ -275,11 +276,11 @@ export class DigestTransform extends Transform {

validate() {
if (this._actual == null) {
throw new Error("Not finished yet")
throw newError("Not finished yet", "ERR_STREAM_NOT_FINISHED")
}

if (this._actual !== this.expected) {
throw new Error(`${this.algorithm} checksum mismatch, expected ${this.expected}, got ${this._actual}`)
throw newError(`${this.algorithm} checksum mismatch, expected ${this.expected}, got ${this._actual}`, "ERR_CHECKSUM_MISMATCH")
}

return null
Expand Down
6 changes: 6 additions & 0 deletions packages/builder-util-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,10 @@ export function asArray<T>(v: null | undefined | T | Array<T>): Array<T> {
else {
return [v]
}
}

export function newError(message: string, code: string) {
const error = new Error(message);
(error as any).code = code
return error
}
5 changes: 3 additions & 2 deletions packages/builder-util-runtime/src/uuid.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createHash, randomBytes } from "crypto"
import { newError } from "./index"

const invalidName =
"options.name must be either a string or a Buffer"
Expand Down Expand Up @@ -123,7 +124,7 @@ export class UUID {
}
}

throw new Error("Unknown type of uuid")
throw newError("Unknown type of uuid", "ERR_UNKNOWN_UUID_TYPE")
}

// read stringified uuid into a Buffer
Expand Down Expand Up @@ -248,7 +249,7 @@ function uuidNamed(name: string | Buffer, hashMethod: string, version: number, n

const nameIsNotAString = typeof name !== "string"
if (nameIsNotAString && !Buffer.isBuffer(name)) {
throw new Error(invalidName)
throw newError(invalidName, "ERR_INVALID_UUID_NAME")
}

hash.update(namespace)
Expand Down
9 changes: 5 additions & 4 deletions packages/builder-util-runtime/src/xml.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as sax from "sax"
import { newError } from "./index"

export class XElement {
value = ""
Expand All @@ -8,17 +9,17 @@ export class XElement {

constructor(readonly name: string) {
if (!name) {
throw new Error("Element name cannot be empty")
throw newError("Element name cannot be empty", "ERR_XML_ELEMENT_NAME_EMPTY")
}
if (!isValidName(name)) {
throw new Error(`Invalid element name: ${name}`)
throw newError(`Invalid element name: ${name}`, "ERR_XML_ELEMENT_INVALID_NAME")
}
}

attribute(name: string): string {
const result = this.attributes === null ? null : this.attributes[name]
if (result == null) {
throw new Error(`No attribute "${name}"`)
throw newError(`No attribute "${name}"`, "ERR_XML_MISSED_ATTRIBUTE")
}
return result
}
Expand All @@ -32,7 +33,7 @@ export class XElement {
element(name: string, ignoreCase = false, errorIfMissed: string | null = null): XElement {
const result = this.elementOrNull(name, ignoreCase)
if (result === null) {
throw new Error(errorIfMissed || `No element "${name}"`)
throw newError(errorIfMissed || `No element "${name}"`, "ERR_XML_MISSED_ELEMENT")
}
return result
}
Expand Down
6 changes: 3 additions & 3 deletions packages/electron-updater/src/AppImageUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BluebirdPromise from "bluebird-lst"
import { AllPublishOptions, CancellationToken, DownloadOptions, UpdateInfo } from "builder-util-runtime"
import { AllPublishOptions, CancellationToken, DownloadOptions, newError, UpdateInfo } from "builder-util-runtime"
import { execFileSync, spawn } from "child_process"
import isDev from "electron-is-dev"
import { chmod, unlinkSync } from "fs-extra-p"
Expand Down Expand Up @@ -51,7 +51,7 @@ export class AppImageUpdater extends BaseUpdater {

const oldFile = process.env.APPIMAGE!!
if (oldFile == null) {
throw new Error("APPIMAGE env is not defined")
throw newError("APPIMAGE env is not defined", "ERR_UPDATER_OLD_FILE_NOT_FOUND")
}

let isDownloadFull = false
Expand Down Expand Up @@ -88,7 +88,7 @@ export class AppImageUpdater extends BaseUpdater {
protected doInstall(installerPath: string, isSilent: boolean, isRunAfter: boolean): boolean {
const appImageFile = process.env.APPIMAGE!!
if (appImageFile == null) {
throw new Error("APPIMAGE env is not defined")
throw newError("APPIMAGE env is not defined", "ERR_UPDATER_OLD_FILE_NOT_FOUND")
}

// https://stackoverflow.com/a/1712051/1910191
Expand Down
10 changes: 5 additions & 5 deletions packages/electron-updater/src/AppUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BluebirdPromise from "bluebird-lst"
import { AllPublishOptions, asArray, CancellationToken, PublishConfiguration, UpdateInfo, UUID } from "builder-util-runtime"
import { AllPublishOptions, asArray, CancellationToken, newError, PublishConfiguration, UpdateInfo, UUID } from "builder-util-runtime"
import { randomBytes } from "crypto"
import { Notification } from "electron"
import isDev from "electron-is-dev"
Expand Down Expand Up @@ -63,10 +63,10 @@ export abstract class AppUpdater extends EventEmitter {
set channel(value: string | null) {
if (this._channel != null) {
if (typeof value !== "string") {
throw new Error(`Channel must be a string, but got: ${value}`)
throw newError(`Channel must be a string, but got: ${value}`, "ERR_UPDATER_INVALID_CHANNEL")
}
else if (value.length === 0) {
throw new Error(`Channel must be not an empty string`)
throw newError(`Channel must be not an empty string`, "ERR_UPDATER_INVALID_CHANNEL")
}
}

Expand Down Expand Up @@ -160,7 +160,7 @@ export abstract class AppUpdater extends EventEmitter {
const currentVersionString = this.app.getVersion()
const currentVersion = parseVersion(currentVersionString)
if (currentVersion == null) {
throw new Error(`App version is not a valid semver version: "${currentVersionString}`)
throw newError(`App version is not a valid semver version: "${currentVersionString}`, "ERR_UPDATER_INVALID_VERSION")
}
this.currentVersion = currentVersion

Expand Down Expand Up @@ -279,7 +279,7 @@ export abstract class AppUpdater extends EventEmitter {

const latestVersion = parseVersion(updateInfo.version)
if (latestVersion == null) {
throw new Error(`Latest version (from update server) is not valid semver version: "${latestVersion}`)
throw newError(`Latest version (from update server) is not valid semver version: "${latestVersion}`, "ERR_UPDATER_INVALID_VERSION")
}

const isStagingMatch = await this.isStagingMatch(updateInfo)
Expand Down
6 changes: 3 additions & 3 deletions packages/electron-updater/src/BintrayProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BintrayOptions, CancellationToken, HttpExecutor, UpdateInfo } from "builder-util-runtime"
import { BintrayOptions, CancellationToken, HttpExecutor, newError, UpdateInfo } from "builder-util-runtime"
import { BintrayClient } from "builder-util-runtime/out/bintray"
import { URL } from "url"
import { getChannelFilename, getDefaultChannelName, newBaseUrl, Provider, ResolvedUpdateFileInfo } from "./main"
Expand Down Expand Up @@ -28,15 +28,15 @@ export class BintrayProvider extends Provider<UpdateInfo> {
const channelFile = files.find(it => it.name.endsWith(`_${channelFilename}`) || it.name.endsWith(`-${channelFilename}`))
if (channelFile == null) {
// noinspection ExceptionCaughtLocallyJS
throw new Error(`Cannot find channel file "${channelFilename}", existing files:\n${files.map(it => JSON.stringify(it, null, 2)).join(",\n")}`)
throw newError(`Cannot find channel file "${channelFilename}", existing files:\n${files.map(it => JSON.stringify(it, null, 2)).join(",\n")}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND")
}

const channelFileUrl = new URL(`https://dl.bintray.com/${this.client.owner}/${this.client.repo}/${channelFile.name}`)
return parseUpdateInfo(await this.httpRequest(channelFileUrl), channelFilename, channelFileUrl)
}
catch (e) {
if ("statusCode" in e && e.statusCode === 404) {
throw new Error(`No latest version, please ensure that user, package and repository correctly configured. Or at least one version is published. ${e.stack || e.message}`)
throw newError(`No latest version, please ensure that user, package and repository correctly configured. Or at least one version is published. ${e.stack || e.message}`, "ERR_UPDATER_LATEST_VERSION_NOT_FOUND")
}
throw e
}
Expand Down
4 changes: 2 additions & 2 deletions packages/electron-updater/src/GenericProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GenericServerOptions, HttpError, UpdateInfo } from "builder-util-runtime"
import { GenericServerOptions, HttpError, newError, UpdateInfo } from "builder-util-runtime"
import { AppUpdater } from "./AppUpdater"
import { getChannelFilename, getCustomChannelName, getDefaultChannelName, isUseOldMacProvider, newBaseUrl, newUrlFromBase, Provider, ResolvedUpdateFileInfo } from "./main"
import { parseUpdateInfo, resolveFiles } from "./Provider"
Expand Down Expand Up @@ -26,7 +26,7 @@ export class GenericProvider extends Provider<UpdateInfo> {
}
catch (e) {
if (e instanceof HttpError && e.statusCode === 404) {
throw new Error(`Cannot find channel "${channelFile}" update info: ${e.stack || e.message}`)
throw newError(`Cannot find channel "${channelFile}" update info: ${e.stack || e.message}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND")
}
else if (e.code === "ECONNREFUSED") {
if (attemptNumber < 3) {
Expand Down
14 changes: 8 additions & 6 deletions packages/electron-updater/src/GitHubProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CancellationToken, GithubOptions, githubUrl, HttpError, HttpExecutor, parseXml, ReleaseNoteInfo, UpdateInfo, XElement } from "builder-util-runtime"
import { CancellationToken, GithubOptions, githubUrl, HttpError, HttpExecutor, newError, parseXml, ReleaseNoteInfo, UpdateInfo, XElement } from "builder-util-runtime"
import * as semver from "semver"
import { URL } from "url"
import { AppUpdater } from "./AppUpdater"
Expand All @@ -9,7 +9,7 @@ export abstract class BaseGitHubProvider<T extends UpdateInfo> extends Provider<
// so, we don't need to parse port (because node http doesn't support host as url does)
protected readonly baseUrl: URL

constructor(protected readonly options: GithubOptions, defaultHost: string, executor: HttpExecutor<any>) {
protected constructor(protected readonly options: GithubOptions, defaultHost: string, executor: HttpExecutor<any>) {
super(executor, false /* because GitHib uses S3 */)

this.baseUrl = newBaseUrl(githubUrl(options, defaultHost))
Expand Down Expand Up @@ -40,18 +40,19 @@ export class GitHubProvider extends BaseGitHubProvider<UpdateInfo> {
let version: string | null
try {
if (this.updater.allowPrerelease) {
// noinspection TypeScriptValidateJSTypes
version = latestRelease.element("link").attribute("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}`)
throw newError(`Cannot parse releases feed: ${e.stack || e.message},\nXML:\n${feedXml}`, "ERR_UPDATER_INVALID_RELEASE_FEED")
}

if (version == null) {
throw new Error(`No published versions on GitHub`)
throw newError(`No published versions on GitHub`, "ERR_UPDATER_NO_PUBLISHED_VERSIONS")
}

const channelFile = getChannelFilename(getDefaultChannelName())
Expand All @@ -63,7 +64,7 @@ export class GitHubProvider extends BaseGitHubProvider<UpdateInfo> {
}
catch (e) {
if (!this.updater.allowPrerelease && e instanceof HttpError && e.statusCode === 404) {
throw new Error(`Cannot find ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}`)
throw newError(`Cannot find ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND")
}
throw e
}
Expand Down Expand Up @@ -96,7 +97,7 @@ export class GitHubProvider extends BaseGitHubProvider<UpdateInfo> {
return (releaseInfo.tag_name.startsWith("v")) ? releaseInfo.tag_name.substring(1) : releaseInfo.tag_name
}
catch (e) {
throw new Error(`Unable to find latest version on GitHub (${url}), please ensure a production release exists: ${e.stack || e.message}`)
throw newError(`Unable to find latest version on GitHub (${url}), please ensure a production release exists: ${e.stack || e.message}`, "ERR_UPDATER_LATEST_VERSION_NOT_FOUND")
}
}

Expand Down Expand Up @@ -131,6 +132,7 @@ export function computeReleaseNotes(currentVersion: string, isFullChangelog: boo

const releaseNotes: Array<ReleaseNoteInfo> = []
for (const release of feed.getElements("entry")) {
// noinspection TypeScriptValidateJSTypes
const versionRelease = release.element("link").attribute("href").match(/\/tag\/v?([^\/]+)$/)![1]
if (semver.lt(currentVersion, versionRelease)) {
releaseNotes.push({
Expand Down
4 changes: 2 additions & 2 deletions packages/electron-updater/src/MacUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BluebirdPromise from "bluebird-lst"
import { AllPublishOptions, CancellationToken, configureRequestOptionsFromUrl, DigestTransform, ProgressCallbackTransform, RequestHeaders, safeGetHeader, safeStringifyJson, UpdateInfo } from "builder-util-runtime"
import { AllPublishOptions, CancellationToken, configureRequestOptionsFromUrl, DigestTransform, newError, ProgressCallbackTransform, RequestHeaders, safeGetHeader, safeStringifyJson, UpdateInfo } from "builder-util-runtime"
import { createServer, IncomingMessage, OutgoingHttpHeaders, ServerResponse } from "http"
import { AppUpdater } from "./AppUpdater"
import { DOWNLOAD_PROGRESS, UPDATE_DOWNLOADED } from "./main"
Expand All @@ -26,7 +26,7 @@ export class MacUpdater extends AppUpdater {
const files = (await this.provider).resolveFiles(updateInfo)
const zipFileInfo = findFile(files, "zip", ["pkg", "dmg"])
if (zipFileInfo == null) {
throw new Error(`ZIP file not provided: ${safeStringifyJson(files)}`)
throw newError(`ZIP file not provided: ${safeStringifyJson(files)}`, "ERR_UPDATER_ZIP_FILE_NOT_FOUND")
}

const server = createServer()
Expand Down
4 changes: 2 additions & 2 deletions packages/electron-updater/src/NsisUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AllPublishOptions, CancellationToken, DownloadOptions, PackageFileInfo, UpdateInfo } from "builder-util-runtime"
import { AllPublishOptions, CancellationToken, DownloadOptions, newError, PackageFileInfo, UpdateInfo } from "builder-util-runtime"
import { spawn } from "child_process"
import { OutgoingHttpHeaders } from "http"
import * as path from "path"
Expand Down Expand Up @@ -44,7 +44,7 @@ export class NsisUpdater extends BaseUpdater {
if (signatureVerificationStatus != null) {
await removeTempDirIfAny()
// noinspection ThrowInsideFinallyBlockJS
throw new Error(`New version ${this.updateInfo!.version} is not signed by the application owner: ${signatureVerificationStatus}`)
throw newError(`New version ${this.updateInfo!.version} is not signed by the application owner: ${signatureVerificationStatus}`, "ERR_UPDATER_INVALID_SIGNATURE")
}

const packageInfo = fileInfo.packageInfo
Expand Down
10 changes: 5 additions & 5 deletions packages/electron-updater/src/PrivateGitHubProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CancellationToken, GithubOptions, HttpError, HttpExecutor, UpdateInfo } from "builder-util-runtime"
import { CancellationToken, GithubOptions, HttpError, HttpExecutor, newError, UpdateInfo } from "builder-util-runtime"
import { OutgoingHttpHeaders, RequestOptions } from "http"
import { safeLoad } from "js-yaml"
import * as path from "path"
Expand Down Expand Up @@ -31,7 +31,7 @@ export class PrivateGitHubProvider extends BaseGitHubProvider<PrivateGitHubUpdat
const asset = releaseInfo.assets.find(it => it.name === channelFile)
if (asset == null) {
// html_url must be always, but just to be sure
throw new Error(`Cannot find ${channelFile} in the release ${releaseInfo.html_url || releaseInfo.name}`)
throw newError(`Cannot find ${channelFile} in the release ${releaseInfo.html_url || releaseInfo.name}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND")
}

const url = new URL(asset.url)
Expand All @@ -41,7 +41,7 @@ export class PrivateGitHubProvider extends BaseGitHubProvider<PrivateGitHubUpdat
}
catch (e) {
if (e instanceof HttpError && e.statusCode === 404) {
throw new Error(`Cannot find ${channelFile} in the latest release artifacts (${url}): ${e.stack || e.message}`)
throw newError(`Cannot find ${channelFile} in the latest release artifacts (${url}): ${e.stack || e.message}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND")
}
throw e
}
Expand All @@ -67,7 +67,7 @@ export class PrivateGitHubProvider extends BaseGitHubProvider<PrivateGitHubUpdat
return (JSON.parse((await this.httpRequest(url, this.configureHeaders("application/vnd.github.v3+json"), cancellationToken))!!))
}
catch (e) {
throw new Error(`Unable to find latest version on GitHub (${url}), please ensure a production release exists: ${e.stack || e.message}`)
throw newError(`Unable to find latest version on GitHub (${url}), please ensure a production release exists: ${e.stack || e.message}`, "ERR_UPDATER_LATEST_VERSION_NOT_FOUND")
}
}

Expand All @@ -80,7 +80,7 @@ export class PrivateGitHubProvider extends BaseGitHubProvider<PrivateGitHubUpdat
const name = path.posix.basename(it.url).replace(/ /g, "-")
const asset = updateInfo.assets.find(it => it != null && it.name === name)
if (asset == null) {
throw new Error(`Cannot find asset "${name}" in: ${JSON.stringify(updateInfo.assets, null, 2)}`)
throw newError(`Cannot find asset "${name}" in: ${JSON.stringify(updateInfo.assets, null, 2)}`, "ERR_UPDATER_ASSET_NOT_FOUND")
}

return {
Expand Down
12 changes: 6 additions & 6 deletions packages/electron-updater/src/Provider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CancellationToken, HttpExecutor, safeStringifyJson, UpdateFileInfo, UpdateInfo, WindowsUpdateInfo } from "builder-util-runtime"
import { CancellationToken, HttpExecutor, newError, safeStringifyJson, UpdateFileInfo, UpdateInfo, WindowsUpdateInfo } from "builder-util-runtime"
import { OutgoingHttpHeaders, RequestOptions } from "http"
import { safeLoad } from "js-yaml"
import { URL } from "url"
Expand Down Expand Up @@ -49,7 +49,7 @@ export abstract class Provider<T extends UpdateInfo> {

export function findFile(files: Array<ResolvedUpdateFileInfo>, extension: string, not?: Array<string>): ResolvedUpdateFileInfo | null | undefined {
if (files.length === 0) {
throw new Error("No files provided")
throw newError("No files provided", "ERR_UPDATER_NO_FILES_PROVIDED")
}

const result = files.find(it => it.url.pathname.toLowerCase().endsWith(`.${extension}`))
Expand All @@ -66,15 +66,15 @@ export function findFile(files: Array<ResolvedUpdateFileInfo>, extension: string

export function parseUpdateInfo(rawData: string | null, channelFile: string, channelFileUrl: URL): UpdateInfo {
if (rawData == null) {
throw new Error(`Cannot parse update info from ${channelFile} in the latest release artifacts (${channelFileUrl}): rawData: null`)
throw newError(`Cannot parse update info from ${channelFile} in the latest release artifacts (${channelFileUrl}): rawData: null`, "ERR_UPDATER_INVALID_UPDATE_INFO")
}

let result: UpdateInfo
try {
result = safeLoad(rawData)
}
catch (e) {
throw new Error(`Cannot parse update info from ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}, rawData: ${rawData}`)
throw newError(`Cannot parse update info from ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}, rawData: ${rawData}`, "ERR_UPDATER_INVALID_UPDATE_INFO")
}
return result
}
Expand All @@ -94,15 +94,15 @@ export function getFileList(updateInfo: UpdateInfo): Array<UpdateFileInfo> {
]
}
else {
throw new Error(`No files provided: ${safeStringifyJson(updateInfo)}`)
throw newError(`No files provided: ${safeStringifyJson(updateInfo)}`, "ERR_UPDATER_NO_FILES_PROVIDED")
}
}

export function resolveFiles(updateInfo: UpdateInfo, baseUrl: URL, pathTransformer: (p: string) => string = p => p): Array<ResolvedUpdateFileInfo> {
const files = getFileList(updateInfo)
const result: Array<ResolvedUpdateFileInfo> = files.map(fileInfo => {
if ((fileInfo as any).sha2 == null && fileInfo.sha512 == null) {
throw new Error(`Update info doesn't contain nor sha256 neither sha512 checksum: ${safeStringifyJson(fileInfo)}`)
throw newError(`Update info doesn't contain nor sha256 neither sha512 checksum: ${safeStringifyJson(fileInfo)}`, "ERR_UPDATER_NO_CHECKSUM")
}
return {
url: newUrlFromBase(pathTransformer(fileInfo.url), baseUrl),
Expand Down
Loading

0 comments on commit 2822049

Please sign in to comment.