diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml
index 35ae13010f8..c18c70f3813 100644
--- a/.idea/dictionaries/develar.xml
+++ b/.idea/dictionaries/develar.xml
@@ -7,6 +7,7 @@
appimage
appleid
appveyor
+ archiver
archs
aspx
atime
@@ -88,6 +89,7 @@
promisify
psmdcp
readpass
+ rels
repos
rimraf
semver
diff --git a/.travis.yml b/.travis.yml
index 668ebaabca1..0c0c4639d9a 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,4 @@
-osx_image: xcode7.3
+osx_image: xcode8
matrix:
include:
diff --git a/docker/winSign.sh b/docker/winSign.sh
deleted file mode 100755
index 9a48e8bc86a..00000000000
--- a/docker/winSign.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env bash
-
-dir=${PWD##*/}
-rm -rf ../${dir}.7z
-7za a -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ../winCodeSign.7z .
diff --git a/package.json b/package.json
index 4aecb80bd36..571660248db 100644
--- a/package.json
+++ b/package.json
@@ -60,6 +60,8 @@
"dependencies": {
"7zip-bin": "^1.0.6",
"ansi-escapes": "^1.4.0",
+ "archiver": "^1.0.1",
+ "archiver-utils": "^1.2.0",
"asar-electron-builder": "^0.13.2",
"bluebird": "^3.4.1",
"chalk": "^1.1.3",
@@ -69,7 +71,6 @@
"debug": "^2.2.0",
"electron-download": "^2.1.2",
"electron-osx-sign": "^0.4.0-beta4",
- "electron-winstaller-fixed": "~4.0.0",
"extract-zip": "^1.5.0",
"fs-extra-p": "^1.0.6",
"hosted-git-info": "^2.1.5",
@@ -102,7 +103,6 @@
]
},
"devDependencies": {
- "path-sort": "^0.1.0",
"@develar/semantic-release": "^6.3.6",
"@types/debug": "0.0.28",
"@types/mime": "0.0.28",
@@ -117,6 +117,7 @@
"decompress-zip": "^0.3.0",
"diff": "^2.2.3",
"json8": "^0.9.2",
+ "path-sort": "^0.1.0",
"pre-git": "^3.10.0",
"ts-babel": "^1.0.4",
"tslint": "^3.14.0-dev.1",
diff --git a/src/targets/nsis.ts b/src/targets/nsis.ts
index afa2a1a846a..7092c1876af 100644
--- a/src/targets/nsis.ts
+++ b/src/targets/nsis.ts
@@ -3,7 +3,7 @@ import { Arch, NsisOptions } from "../metadata"
import { debug, doSpawn, handleProcess, use } from "../util/util"
import * as path from "path"
import { Promise as BluebirdPromise } from "bluebird"
-import { getBin } from "../util/binDownload"
+import { getBinFromBintray } from "../util/binDownload"
import { v5 as uuid5 } from "uuid-1345"
import { Target } from "../platformPackager"
import { archiveApp } from "./archive"
@@ -14,14 +14,14 @@ import semver = require("semver")
//noinspection JSUnusedLocalSymbols
const __awaiter = require("../util/awaiter")
-const NSIS_VERSION = "nsis-3.0.0"
+const NSIS_VERSION = "3.0.0"
//noinspection SpellCheckingInspection
const NSIS_SHA2 = "7741089f3ca13de879f87836156ef785eab49844cacbeeabaeaefd1ade325ee7"
//noinspection SpellCheckingInspection
const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3"
-const nsisPathPromise = getBin("nsis", NSIS_VERSION, `https://dl.bintray.com/electron-userland/bin/${NSIS_VERSION}.7z`, NSIS_SHA2)
+const nsisPathPromise = getBinFromBintray("nsis", NSIS_VERSION, NSIS_SHA2)
export default class NsisTarget extends Target {
private readonly options: NsisOptions
diff --git a/src/targets/squirrelPack.ts b/src/targets/squirrelPack.ts
new file mode 100644
index 00000000000..4c8ac2beb66
--- /dev/null
+++ b/src/targets/squirrelPack.ts
@@ -0,0 +1,250 @@
+import * as path from "path"
+import { Promise as BluebirdPromise } from "bluebird"
+import { emptyDir, copy, createWriteStream, unlink } from "fs-extra-p"
+import { spawn, exec } from "../util/util"
+import { debug } from "../util/util"
+import { WinPackager } from "../winPackager"
+
+const archiverUtil = require("archiver-utils")
+const archiver = require("archiver")
+
+//noinspection JSUnusedLocalSymbols
+const __awaiter = require("../util/awaiter")
+
+export function convertVersion(version: string): string {
+ const parts = version.split("-")
+ const mainVersion = parts.shift()
+ if (parts.length > 0) {
+ return [mainVersion, parts.join("-").replace(/\./g, "")].join("-")
+ }
+ else {
+ return mainVersion!
+ }
+}
+
+function syncReleases(outputDirectory: string, options: SquirrelOptions) {
+ const args = prepareArgs(["-u", options.remoteReleases!, "-r", outputDirectory], path.join(options.vendorPath, "SyncReleases.exe"))
+ if (options.remoteToken) {
+ args.push("-t", options.remoteToken)
+ }
+ return spawn(process.platform === "win32" ? path.join(options.vendorPath, "SyncReleases.exe") : "mono", args)
+}
+
+export interface SquirrelOptions {
+ vendorPath: string
+ remoteReleases?: string
+ remoteToken?: string
+ loadingGif?: string
+ productName?: string
+ name: string
+ packageCompressionLevel?: number
+ version: string
+ msi?: any
+
+ owners?: string
+ description?: string
+ iconUrl?: string
+ authors?: string
+ extraMetadataSpecs?: string
+ copyright?: string
+}
+
+export async function buildInstaller(options: SquirrelOptions, outputDirectory: string, stageDir: string, setupExe: string, packager: WinPackager, appOutDir: string) {
+ const appUpdate = path.join(stageDir, "Update.exe")
+ const promises = [
+ copy(path.join(options.vendorPath, "Update.exe"), appUpdate)
+ .then(() => packager.sign(appUpdate)),
+ emptyDir(outputDirectory)
+ ]
+ if (options.remoteReleases) {
+ promises.push(syncReleases(outputDirectory, options))
+ }
+ await BluebirdPromise.all(promises)
+
+ const embeddedArchiveFile = path.join(stageDir, "setup.zip")
+ const embeddedArchive = archiver("zip", {zlib: {level: options.packageCompressionLevel == null ? 6 : options.packageCompressionLevel}})
+ const embeddedArchiveOut = createWriteStream(embeddedArchiveFile)
+ const embeddedArchivePromise = new BluebirdPromise(function (resolve, reject) {
+ embeddedArchive.on("error", reject)
+ embeddedArchiveOut.on("close", resolve)
+ })
+ embeddedArchive.pipe(embeddedArchiveOut)
+
+ embeddedArchive.file(appUpdate, {name: "Update.exe"})
+ embeddedArchive.file(options.loadingGif ? path.resolve(options.loadingGif) : path.join(__dirname, "..", "..", "templates", "install-spinner.gif"), {name: "background.gif"})
+
+ const version = convertVersion(options.version)
+ const packageName = `${options.name}-${version}-full.nupkg`
+ const nupkgPath = path.join(outputDirectory, packageName)
+ const setupPath = path.join(outputDirectory, setupExe || `${options.name || options.productName}Setup.exe`)
+
+ await BluebirdPromise.all([
+ pack(options, appOutDir, appUpdate, nupkgPath, version, options.packageCompressionLevel),
+ copy(path.join(options.vendorPath, "Setup.exe"), setupPath),
+ ])
+
+ embeddedArchive.file(nupkgPath, {name: packageName})
+
+ const releaseEntry = await releasify(options, nupkgPath, outputDirectory, packageName)
+
+ embeddedArchive.append(releaseEntry, {name: "RELEASES"})
+ embeddedArchive.finalize()
+ await embeddedArchivePromise
+
+ const writeZipToSetup = path.join(options.vendorPath, "WriteZipToSetup.exe")
+ await exec(process.platform === "win32" ? writeZipToSetup : "wine", prepareArgs([setupPath, embeddedArchiveFile], writeZipToSetup))
+
+ await packager.signAndEditResources(setupPath)
+ if (options.msi && process.platform === "win32") {
+ const outFile = setupExe.replace(".exe", ".msi")
+ await msi(options, nupkgPath, setupPath, outputDirectory, outFile)
+ await packager.signAndEditResources(path.join(outputDirectory, outFile))
+ }
+}
+
+async function pack(options: SquirrelOptions, directory: string, updateFile: string, outFile: string, version: string, packageCompressionLevel?: number) {
+ const archive = archiver("zip", {zlib: {level: packageCompressionLevel == null ? 9 : packageCompressionLevel}})
+ // const archiveOut = createWriteStream('/Users/develar/test.zip')
+ const archiveOut = createWriteStream(outFile)
+ const archivePromise = new BluebirdPromise(function (resolve, reject) {
+ archive.on("error", reject)
+ archiveOut.on("close", resolve)
+ })
+ archive.pipe(archiveOut)
+
+ const author = options.authors || options.owners
+ const copyright = options.copyright || `Copyright © ${new Date().getFullYear()} ${author}`
+ const nuspecContent = `
+
+
+ ${options.name}
+ ${version}
+ ${options.productName}
+ ${author}
+ ${options.owners || options.authors}
+ ${options.iconUrl}
+ false
+ ${options.description}
+ ${copyright}${options.extraMetadataSpecs || ""}
+
+`
+ debug(`Created NuSpec file:\n${nuspecContent}`)
+ archive.append(nuspecContent.replace(/\n/, "\r\n"), {name: `${encodeURI(options.name).replace(/%5B/g, "[").replace(/%5D/g, "]")}.nuspec`})
+
+ //noinspection SpellCheckingInspection
+ archive.append(`
+
+
+
+`.replace(/\n/, "\r\n"), {name: ".rels", prefix: "_rels"})
+
+ //noinspection SpellCheckingInspection
+ archive.append(`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`.replace(/\n/, "\r\n"), {name: "[Content_Types].xml"})
+
+ archive.append(`
+
+ ${author}
+ ${options.description}
+ ${options.name}
+ ${version}
+
+ ${options.productName}
+ NuGet, Version=2.8.50926.602, Culture=neutral, PublicKeyToken=null;Microsoft Windows NT 6.2.9200.0;.NET Framework 4
+`.replace(/\n/, "\r\n"), {name: "1.psmdcp", prefix: "package/services/metadata/core-properties"})
+
+ archive.file(updateFile, {name: "Update.exe", prefix: "lib/net45"})
+ encodedZip(archive, directory, "lib/net45")
+ await archivePromise
+}
+
+async function releasify(options: SquirrelOptions, nupkgPath: string, outputDirectory: string, packageName: string) {
+ const args = [
+ "--releasify", nupkgPath,
+ "--releaseDir", outputDirectory
+ ]
+ const out = (await exec(process.platform === "win32" ? path.join(options.vendorPath, "Update.com") : "mono", prepareArgs(args, path.join(options.vendorPath, "Update-Mono.exe")))).trim()
+ if (debug.enabled) {
+ debug(out)
+ }
+
+ const lines = out.split("\n")
+ for (let i = lines.length - 1; i > -1; i--) {
+ const line = lines[i]
+ if (line.includes(packageName)) {
+ return line.trim()
+ }
+ }
+
+ throw new Error("Invalid output, cannot find last release entry")
+}
+
+async function msi(options: SquirrelOptions, nupkgPath: string, setupPath: string, outputDirectory: string, outFile: string) {
+ const args = [
+ "--createMsi", nupkgPath,
+ "--bootstrapperExe", setupPath
+ ]
+ await exec(process.platform === "win32" ? path.join(options.vendorPath, "Update.com") : "mono", prepareArgs(args, path.join(options.vendorPath, "Update-Mono.exe")))
+ //noinspection SpellCheckingInspection
+ await exec(path.join(options.vendorPath, "candle.exe"), ["-nologo", "-ext", "WixNetFxExtension", "-out", "Setup.wixobj", "Setup.wxs"], {
+ cwd: outputDirectory,
+ })
+ //noinspection SpellCheckingInspection
+ await exec(path.join(options.vendorPath, "light.exe"), ["-ext", "WixNetFxExtension", "-sval", "-out", outFile, "Setup.wixobj"], {
+ cwd: outputDirectory,
+ })
+
+ //noinspection SpellCheckingInspection
+ await BluebirdPromise.all([
+ unlink(path.join(outputDirectory, "Setup.wxs")),
+ unlink(path.join(outputDirectory, "Setup.wixobj")),
+ unlink(path.join(outputDirectory, outFile.replace(".msi", ".wixpdb"))).catch(e => debug(e.toString())),
+ ])
+}
+
+function prepareArgs(args: Array, exePath: string) {
+ if (process.platform !== "win32") {
+ args.unshift(exePath)
+ }
+ return args
+}
+
+function encodedZip(archive: any, dir: string, prefix: string) {
+ archiverUtil.walkdir(dir, function (error: any, files: any) {
+ if (error) {
+ archive.emit("error", error)
+ return
+ }
+
+ for (let file of files) {
+ if (file.stats.isDirectory()) {
+ continue
+ }
+
+ // GBK file name encoding (or Non-English file name) caused a problem
+ const entryData = {
+ name: encodeURI(file.relative.replace(/\\/g, "/")).replace(/%5B/g, "[").replace(/%5D/g, "]"),
+ prefix: prefix,
+ stats: file.stats,
+ }
+ archive._append(file.path, entryData)
+ }
+
+ archive.finalize()
+ })
+}
\ No newline at end of file
diff --git a/src/targets/squirrelWindows.ts b/src/targets/squirrelWindows.ts
index c1797e07d1c..deb42b094dc 100644
--- a/src/targets/squirrelWindows.ts
+++ b/src/targets/squirrelWindows.ts
@@ -1,14 +1,22 @@
import { WinPackager } from "../winPackager"
import { getArchSuffix, Target } from "../platformPackager"
-import { Arch, WinBuildOptions } from "../metadata"
-import { createWindowsInstaller, convertVersion } from "electron-winstaller-fixed"
+import { Arch } from "../metadata"
import * as path from "path"
import { warn, log } from "../util/log"
import { getRepositoryInfo } from "../repositoryInfo"
+import { getBinFromBintray } from "../util/binDownload"
+import { tmpdir } from "os"
+import { getTempName } from "../util/util"
+import { emptyDir, remove } from "fs-extra-p"
+import { buildInstaller, convertVersion, SquirrelOptions } from "./squirrelPack"
//noinspection JSUnusedLocalSymbols
const __awaiter = require("../util/awaiter")
+const SW_VERSION = "1.4.4"
+//noinspection SpellCheckingInspection
+const SW_SHA2 = "98e1d81c80d7afc1bcfb37f3b224dc4f761088506b9c28ccd72d1cf8752853ba"
+
export default class SquirrelWindowsTarget extends Target {
constructor(private packager: WinPackager) {
super("squirrel")
@@ -26,8 +34,22 @@ export default class SquirrelWindowsTarget extends Target {
const installerOutDir = path.join(appOutDir, "..", `win${getArchSuffix(arch)}`)
- const distOptions = await this.computeEffectiveDistOptions(appOutDir, installerOutDir, setupFileName)
- await createWindowsInstaller(distOptions)
+ const distOptions = await this.computeEffectiveDistOptions()
+
+ const stageDir = path.join(tmpdir(), getTempName("squirrel-windows-builder"))
+ await emptyDir(stageDir)
+ try {
+ await buildInstaller(distOptions, installerOutDir, stageDir, setupFileName, this.packager, appOutDir)
+ }
+ finally {
+ try {
+ await remove(stageDir)
+ }
+ catch (e) {
+ // ignore
+ }
+ }
+
this.packager.dispatchArtifactCreated(path.join(installerOutDir, setupFileName), `${appInfo.name}-Setup-${version}${archSuffix}.exe`)
const packagePrefix = `${appInfo.name}-${convertVersion(version)}-`
@@ -39,7 +61,7 @@ export default class SquirrelWindowsTarget extends Target {
this.packager.dispatchArtifactCreated(path.join(installerOutDir, "RELEASES"))
}
- async computeEffectiveDistOptions(appOutDir: string, installerOutDir: string, setupExeName: string): Promise {
+ async computeEffectiveDistOptions(): Promise {
const packager = this.packager
let iconUrl = packager.platformSpecificBuildOptions.iconUrl || packager.devMetadata.build.iconUrl
if (iconUrl == null) {
@@ -57,30 +79,17 @@ export default class SquirrelWindowsTarget extends Target {
const appInfo = packager.appInfo
const projectUrl = await appInfo.computePackageUrl()
- const cscInfo = await packager.cscInfo
const options: any = Object.assign({
name: appInfo.name,
productName: appInfo.productName,
- exe: `${appInfo.productFilename}.exe`,
- setupExe: setupExeName,
- msiExe: setupExeName.replace(".exe", ".msi"),
- title: appInfo.productName,
- appDirectory: appOutDir,
- outputDirectory: installerOutDir,
version: appInfo.version,
description: appInfo.description,
authors: appInfo.companyName,
iconUrl: iconUrl,
- setupIcon: await packager.getIconPath(),
- certificateFile: cscInfo == null ? null : cscInfo.file,
- certificatePassword: cscInfo == null ? null : cscInfo.password,
- fixUpPaths: false,
- skipUpdateIcon: true,
- usePackageJson: false,
extraMetadataSpecs: projectUrl == null ? null : `\n ${projectUrl}`,
copyright: appInfo.copyright,
packageCompressionLevel: packager.devMetadata.build.compression === "store" ? 0 : 9,
- sign: this.packager.signAndEditResources.bind(this.packager),
+ vendorPath: await getBinFromBintray("Squirrel.Windows", SW_VERSION, SW_SHA2)
}, packager.platformSpecificBuildOptions)
if (!("loadingGif" in options)) {
diff --git a/src/util/binDownload.ts b/src/util/binDownload.ts
index 09ef083ed4a..b7d3eaf520d 100644
--- a/src/util/binDownload.ts
+++ b/src/util/binDownload.ts
@@ -17,6 +17,11 @@ export function downloadFpm(version: string, osAndArch: string): Promise
.then(it => path.join(it, "fpm"))
}
+export function getBinFromBintray(name: string, version: string, sha2?: string): Promise {
+ const dirName = `${name}-${version}`
+ return getBin(name, dirName, `https://dl.bintray.com/electron-userland/bin/${dirName}.7z`, sha2)
+}
+
export function getBin(name: string, dirName: string, url: string, sha2?: string): Promise {
let promise = versionToPromise.get(dirName)
// if rejected, we will try to download again
diff --git a/src/windowsCodeSign.ts b/src/windowsCodeSign.ts
index a0ef1cc6ce5..3b567321407 100644
--- a/src/windowsCodeSign.ts
+++ b/src/windowsCodeSign.ts
@@ -2,14 +2,14 @@ import { spawn } from "./util/util"
import { rename } from "fs-extra-p"
import * as path from "path"
import { release } from "os"
-import { getBin } from "./util/binDownload"
+import { getBinFromBintray } from "./util/binDownload"
//noinspection JSUnusedLocalSymbols
const __awaiter = require("./util/awaiter")
-const TOOLS_VERSION = "winCodeSign-1.3.0"
+const TOOLS_VERSION = "1.4.0"
export function getSignVendorPath() {
- return getBin("winCodeSign", TOOLS_VERSION, `https://dl.bintray.com/electron-userland/bin/${TOOLS_VERSION}.7z`, "cfe9569f7e5aef605c11704d90a3ce22d2445984b51f145c97140eec68bd9833")
+ return getBinFromBintray("winCodeSign", TOOLS_VERSION, "0496cf9d3c68cf00c3873a20794361c782d355c566f0b31a69422571deffeb69")
}
export interface SignOptions {
diff --git a/templates/install-spinner.gif b/templates/install-spinner.gif
new file mode 100644
index 00000000000..2ee0e124289
--- /dev/null
+++ b/templates/install-spinner.gif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ce636d201ef86ffbf4ee8c8762b4d9dc255be9d5f490d0a22e36fe0c938f7244
+size 44410
diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts
index 46d28198445..9e0ecba1d31 100755
--- a/test/src/helpers/packTester.ts
+++ b/test/src/helpers/packTester.ts
@@ -12,7 +12,7 @@ import { tmpdir } from "os"
import { getArchSuffix, Target } from "out/platformPackager"
import pathSorter = require("path-sort")
import DecompressZip = require("decompress-zip")
-import { convertVersion } from "electron-winstaller-fixed"
+import { convertVersion } from "out/targets/squirrelPack"
import { spawnNpmProduction } from "out/util/util"
//noinspection JSUnusedLocalSymbols
diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts
index 3a7b7403b7c..5c14c97cd09 100755
--- a/test/src/winPackagerTest.ts
+++ b/test/src/winPackagerTest.ts
@@ -145,14 +145,12 @@ class CheckingWinPackager extends WinPackager {
async pack(outDir: string, arch: Arch, targets: Array, postAsyncTasks: Array>): Promise {
// skip pack
- const appOutDir = this.computeAppOutDir(outDir, arch)
-
this.effectivePackOptions = await this.computePackOptions()
const helperClass: typeof SquirrelWindowsTarget = require("out/targets/squirrelWindows").default
- this.effectiveDistOptions = await (new helperClass(this).computeEffectiveDistOptions(appOutDir, "foo", "Foo.exe"))
+ this.effectiveDistOptions = await (new helperClass(this).computeEffectiveDistOptions())
- await this.sign(appOutDir)
+ await this.sign(this.computeAppOutDir(outDir, arch))
}
packageInDistributableFormat(outDir: string, appOutDir: string, arch: Arch, targets: Array, promises: Array>): void {
diff --git a/typings/electron-winstaller-fixed.d.ts b/typings/electron-winstaller-fixed.d.ts
deleted file mode 100644
index 6739ffc9ad8..00000000000
--- a/typings/electron-winstaller-fixed.d.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-declare module "electron-winstaller-fixed" {
- export function createWindowsInstaller(options: any): Promise
-
- export function convertVersion(version: string): string
-}
\ No newline at end of file