Skip to content

Commit

Permalink
WIP: NSIS Auto-Update #529
Browse files Browse the repository at this point in the history
  • Loading branch information
develar committed Aug 4, 2016
1 parent 9731225 commit 090473e
Show file tree
Hide file tree
Showing 18 changed files with 434 additions and 206 deletions.
1 change: 1 addition & 0 deletions .idea/electron-builder.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ matrix:
env: TEST_FILES=BuildTest,linuxPackagerTest,globTest NODE_VERSION=6 PUBLISH_TO_NPM=true

- os: osx
env: TEST_FILES=macPackagerTest,winPackagerTest NODE_VERSION=6
env: TEST_FILES=macPackagerTest,winPackagerTest,nsisTest NODE_VERSION=6

- os: osx
env: TEST_FILES=macPackagerTest,CodeSignTest NODE_VERSION=4
Expand Down
25 changes: 25 additions & 0 deletions nsis-auto-updater/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "nsis-auto-updater",
"version": "0.0.1",
"description": "NSIS Auto Updater",
"main": "out/nsis-auto-updater/src/nsis-updater.js",
"scripts": {
},
"author": "Vladimir Krivosheev",
"license": "MIT",
"files": [
"out"
],
"dependencies": {
"bluebird": "^3.4.1",
"fs-extra-p": "^1.0.6"
},
"bundledDependencies": [
"fs-extra-p",
"bluebird"
],
"devDependencies": {
"@types/electron": "^0.37.14",
"@types/node": "^4.0.30"
}
}
62 changes: 62 additions & 0 deletions nsis-auto-updater/src/nsis-updater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { EventEmitter } from "events"
import { app } from "electron"
import { spawn } from "child_process"
import * as path from "path"
import { tmpdir } from "os"

class NsisUpdater extends EventEmitter {
private setupPath = path.join(tmpdir(), 'innobox-upgrade.exe')

private updateAvailable = false
private quitAndInstallCalled = false

constructor(public updateUrl?: string) {
super()
}

getFeedURL(): string | null | undefined {
return this.updateUrl
}

setFeedURL(value: string) {
this.updateUrl = value
}

checkForUpdates(): void {
if (this.updateUrl == null) {
this.emitError("Update URL is not set")
return
}

this.emit("checking-for-update")
}

quitAndInstall(): void {
if (!this.updateAvailable) {
this.emitError("No update available, can't quit and install")
return
}

if (this.quitAndInstallCalled) {
return
}

// prevent calling several times
this.quitAndInstallCalled = true

spawn(this.setupPath, ["/S"], {
detached: true,
stdio: "ignore",
}).unref()

app.quit()
}

// emit both error object and message, this is to keep compatibility with old APIs
private emitError (message: string) {
return this.emit("error", new Error(message), message)
}
}

const updater = new NsisUpdater()
export= updater
29 changes: 29 additions & 0 deletions nsis-auto-updater/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"outDir": "out",
"newLine": "LF",
"noResolve": true,
"noEmitOnError": true,
"inlineSources": true,
"sourceMap": true,
"noImplicitReturns": true,
"strictNullChecks": true,
"noEmitHelpers": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true
},
"files": [
"../node_modules/fs-extra-p/index.d.ts",
"../node_modules/fs-extra-p/bluebird.d.ts",
"../src/util/httpRequest.ts"
],
"include": [
"src/**/*.ts",
"node_modules/@types/**/*.d.ts"
],
"exclude": [
]
}
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"compile": "npm run compile-production && npm run compile-test",
"compile-production": "ts-babel",
"compile-test": "ts-babel test",
"compile-updater": "tsc -p nsis-auto-updater",
"lint": "tslint 'src/**/*.ts' 'test/src/**/*.ts'",
"pretest": "npm run compile && npm run lint",
"test": "node ./test/out/helpers/runTests.js",
Expand Down Expand Up @@ -58,7 +59,7 @@
"bugs": "https://github.com/electron-userland/electron-builder/issues",
"homepage": "https://github.com/electron-userland/electron-builder",
"dependencies": {
"7zip-bin": "^1.0.5",
"7zip-bin": "^1.0.6",
"ansi-escapes": "^1.4.0",
"asar-electron-builder": "^0.13.2",
"bluebird": "^3.4.1",
Expand Down Expand Up @@ -109,7 +110,7 @@
"@types/debug": "0.0.28",
"@types/mime": "0.0.28",
"@types/progress": "^1.1.27",
"@types/semver": "^4.3.26",
"@types/semver": "^4.3.27",
"@types/source-map-support": "^0.2.27",
"ava-tf": "^0.15.4",
"babel-plugin-array-includes": "^2.0.3",
Expand All @@ -121,9 +122,9 @@
"json8": "^0.9.2",
"pre-git": "^3.10.0",
"should": "^10.0.0",
"ts-babel": "^1.0.3",
"ts-babel": "^1.0.4",
"tslint": "3.14.0",
"typescript": "^2.1.0-dev.20160801",
"typescript": "^2.1.0-dev.20160802",
"whitespace": "^2.0.0"
},
"babel": {
Expand Down
102 changes: 102 additions & 0 deletions src/publish/BintrayPublisher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Publisher, PublishOptions } from "./publisher"
import { Promise as BluebirdPromise } from "bluebird"
import { bintrayRequest, HttpError, doApiRequest, uploadFile } from "./gitHubRequest"
import { log } from "../util/log"
import { debug } from "../util/util"
import { basename } from "path"
import { stat } from "fs-extra-p"

//noinspection JSUnusedLocalSymbols
const __awaiter = require("../util/awaiter")

//noinspection ReservedWordAsName
interface Version {
readonly name: string
readonly package: string
}

export class BintrayPublisher implements Publisher {
private _versionPromise: BluebirdPromise<Version>
private readonly auth: string

private basePath: string

constructor(private user: string, apiKey: string, private version: string, private packageName: string, private repo: string = "generic", private options: PublishOptions = {}) {
this.auth = `Basic ${new Buffer(`${user}:${apiKey}`).toString("base64")}`
this.basePath = `/packages/${this.user}/${this.repo}/${this.packageName}`
this._versionPromise = <BluebirdPromise<Version>>this.init()
}

private async init(): Promise<Version | null> {
try {
return await bintrayRequest<Version>(`${this.basePath}/versions/${this.version}`, this.auth)
}
catch (e) {
if (e instanceof HttpError && e.response.statusCode === 404) {
if (this.options.publish !== "onTagOrDraft") {
log(`Version ${this.version} doesn't exist, creating one`)
return this.createVersion()
}
else {
log(`Version ${this.version} doesn't exist, artifacts will be not published`)
}
}

throw e
}
}

private createVersion() {
return bintrayRequest<Version>(`${this.basePath}/versions`, this.auth, {
name: this.version,
})
}

async upload(file: string, artifactName?: string): Promise<any> {
const fileName = artifactName || basename(file)
const version = await this._versionPromise
if (version == null) {
debug(`Version ${this.version} doesn't exist and is not created, artifact ${fileName} is not published`)
return
}

const fileStat = await stat(file)
let badGatewayCount = 0
for (let i = 0; i < 3; i++) {
try {
return await doApiRequest<any>({
hostname: "api.bintray.com",
path: `/content/${this.user}/${this.repo}/${this.packageName}/${version.name}/${fileName}`,
method: "PUT",
headers: {
"User-Agent": "electron-builder",
"Content-Length": fileStat.size,
"X-Bintray-Override": "1",
"X-Bintray-Publish": "1",
}
}, this.auth, uploadFile.bind(this, file, fileStat, fileName))
}
catch (e) {
if (e instanceof HttpError && e.response.statusCode === 502 && badGatewayCount++ < 3) {
continue
}

throw e
}
}
}

//noinspection JSUnusedGlobalSymbols
deleteRelease(): Promise<any> {
if (!this._versionPromise.isFulfilled()) {
return BluebirdPromise.resolve()
}

const version = this._versionPromise.value()
if (version == null) {
return BluebirdPromise.resolve()
}

return bintrayRequest<Version>(`/packages/${this.user}/${this.repo}/${this.packageName}/versions/${version.name}`, this.auth, null, "DELETE")
}
}
44 changes: 11 additions & 33 deletions src/publish/gitHubPublisher.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { isEmptyOrSpaces } from "../util/util"
import { log, warn } from "../util/log"
import { debug } from "../util/util"
import { basename } from "path"
import { parse as parseUrl } from "url"
import * as mime from "mime"
import { stat } from "fs-extra-p"
import { createReadStream } from "fs"
import { gitHubRequest, HttpError, doGitHubRequest } from "./gitHubRequest"
import { gitHubRequest, HttpError, doApiRequest, uploadFile } from "./gitHubRequest"
import { Promise as BluebirdPromise } from "bluebird"
import { ReadStream } from "tty"
import progressStream = require("progress-stream")
import ProgressBar = require("progress")
import { PublishPolicy, PublishOptions, Publisher } from "./publisher"

//noinspection JSUnusedLocalSymbols
Expand Down Expand Up @@ -85,11 +82,11 @@ export class GitHubPublisher implements Publisher {
}

if (createReleaseIfNotExists) {
log(`Release with tag ${this.tag} doesn't exists, creating one`)
log(`Release with tag ${this.tag} doesn't exist, creating one`)
return this.createRelease()
}
else {
log(`Cannot found release with tag ${this.tag}, artifacts will be not published`)
log(`Release with tag ${this.tag} doesn't exist, artifacts will be not published`)
return null
}
}
Expand All @@ -98,45 +95,26 @@ export class GitHubPublisher implements Publisher {
const fileName = artifactName || basename(file)
const release = await this.releasePromise
if (release == null) {
debug(`Release with tag ${this.tag} doesn't exist and is not created, artifact ${fileName} is not published`)
return
}

const parsedUrl = parseUrl(release.upload_url.substring(0, release.upload_url.indexOf("{")) + "?name=" + fileName)
const fileStat = await stat(file)
let badGatewayCount = 0
uploadAttempt: for (let i = 0; i < 3; i++) {
const progressBar = (<ReadStream>process.stdin).isTTY ? new ProgressBar(`Uploading ${fileName} [:bar] :percent :etas`, {
total: fileStat.size,
incomplete: " ",
stream: process.stdout,
width: 20,
}) : null

try {
return await doGitHubRequest<any>({
return await doApiRequest<any>({
hostname: parsedUrl.hostname,
path: parsedUrl.path,
method: "POST",
headers: {
Accept: "application/vnd.github.v3+json",
"User-Agent": "electron-complete-builder",
"User-Agent": "electron-builder",
"Content-Type": mime.lookup(fileName),
"Content-Length": fileStat.size
}
}, this.token, (request, reject) => {
const fileInputStream = createReadStream(file)
fileInputStream.on("error", reject)
fileInputStream
.pipe(progressStream({
length: fileStat.size,
time: 1000
}, progress => {
if (progressBar != null) {
progressBar.tick(progress.delta)
}
}))
.pipe(request)
})
}, this.token, uploadFile.bind(this, file, fileStat, fileName))
}
catch (e) {
if (e instanceof HttpError) {
Expand Down Expand Up @@ -167,7 +145,7 @@ export class GitHubPublisher implements Publisher {
private createRelease() {
return gitHubRequest<Release>(`/repos/${this.owner}/${this.repo}/releases`, this.token, {
tag_name: this.tag,
name: this.tag,
name: this.version,
draft: this.options.draft == null || this.options.draft,
prerelease: this.options.prerelease != null && this.options.prerelease,
})
Expand All @@ -182,12 +160,12 @@ export class GitHubPublisher implements Publisher {
//noinspection JSUnusedGlobalSymbols
async deleteRelease(): Promise<any> {
if (!this._releasePromise.isFulfilled()) {
return BluebirdPromise.resolve()
return
}

const release = this._releasePromise.value()
if (release == null) {
return BluebirdPromise.resolve()
return
}

for (let i = 0; i < 3; i++) {
Expand Down
Loading

0 comments on commit 090473e

Please sign in to comment.