diff --git a/package.json b/package.json index 0fb45902c8f..a9a6aaf37ff 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "deep-assign": "^2.0.0", "electron-osx-sign-tf": "0.6.0", "electron-packager-tf": "~7.3.0", - "electron-winstaller-fixed": "~2.9.5", + "electron-winstaller-fixed": "~2.9.6", "fs-extra-p": "^1.0.1", "glob": "^7.0.3", "hosted-git-info": "^2.1.5", diff --git a/src/index.ts b/src/index.ts index 0ca0b00cb1e..0ff481389b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export { Packager } from "./packager" -export { PackagerOptions, ArtifactCreated, DIR_TARGET } from "./platformPackager" +export { PackagerOptions, ArtifactCreated, DIR_TARGET, BuildInfo } from "./platformPackager" export { BuildOptions, build, createPublisher, CliOptions, createTargets } from "./builder" export { PublishOptions, Publisher } from "./gitHubPublisher" export { AppMetadata, DevMetadata, Platform, Arch, archFromString, getProductName, BuildMetadata, OsXBuildOptions, WinBuildOptions, LinuxBuildOptions } from "./metadata" \ No newline at end of file diff --git a/src/osxPackager.ts b/src/osxPackager.ts index a617e5aa992..b8fcf05ab8d 100644 --- a/src/osxPackager.ts +++ b/src/osxPackager.ts @@ -20,13 +20,9 @@ export default class OsXPackager extends PlatformPackager { this.codeSigningInfo = BluebirdPromise.resolve(null) } else { - if (this.options.cscKeyPassword == null) { - throw new Error("cscLink is set, but cscKeyPassword not") - } - const keychainName = generateKeychainName() cleanupTasks.push(() => deleteKeychain(keychainName)) - this.codeSigningInfo = createKeychain(keychainName, this.options.cscLink, this.options.cscKeyPassword, this.options.cscInstallerLink, this.options.cscInstallerKeyPassword) + this.codeSigningInfo = createKeychain(keychainName, this.options.cscLink, this.getCscPassword(), this.options.cscInstallerLink, this.options.cscInstallerKeyPassword) } } diff --git a/src/platformPackager.ts b/src/platformPackager.ts index 9ba9f7507fa..d391927f25c 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -6,7 +6,7 @@ import * as path from "path" import { pack, ElectronPackagerOptions } from "electron-packager-tf" import { globby } from "./globby" import { readdir, copy, unlink, lstat, remove } from "fs-extra-p" -import { statOrNull, use, spawn, debug7zArgs, debug, warn } from "./util" +import { statOrNull, use, spawn, debug7zArgs, debug, warn, log } from "./util" import { Packager } from "./packager" import { listPackage, statFile, AsarFileMetadata, createPackageFromFiles, AsarOptions } from "asar" import { path7za } from "7zip-bin" @@ -105,6 +105,17 @@ export abstract class PlatformPackager }) } + protected getCscPassword(): string { + const password = this.options.cscKeyPassword + if (password == null) { + log("CSC_KEY_PASSWORD is not defined, empty password will be used") + return "" + } + else { + return password.trim() + } + } + public computeEffectiveTargets(rawList: Array): Array { let targets = normalizeTargets(rawList.length === 0 ? this.customBuildOptions.target : rawList) if (targets != null) { diff --git a/src/util.ts b/src/util.ts index b917284c858..87cd7f9b039 100644 --- a/src/util.ts +++ b/src/util.ts @@ -215,10 +215,11 @@ export function debug7zArgs(command: "a" | "x"): Array { } let tmpDirCounter = 0 -const pidAsString = process.pid.toString(36) +// add date to avoid use stale temp dir +const tempDirPrefix = `${process.pid.toString(36)}-${Date.now().toString(36)}` export function getTempName(prefix?: string | n): string { - return `${prefix == null ? "" : prefix + "-"}${pidAsString}-${tmpDirCounter++}-${Date.now().toString(36)}` + return `${prefix == null ? "" : prefix + "-"}${tempDirPrefix}-${(tmpDirCounter++).toString(36)}` } export function isEmptyOrSpaces(s: string | n) { diff --git a/src/winPackager.ts b/src/winPackager.ts index 62f7d3c64a1..8ddae05d9e9 100644 --- a/src/winPackager.ts +++ b/src/winPackager.ts @@ -5,29 +5,45 @@ import { Platform, WinBuildOptions, Arch } from "./metadata" import * as path from "path" import { log, warn } from "./util" import { deleteFile, emptyDir, open, close, read } from "fs-extra-p" -import { sign } from "signcode-tf" +import { sign, SignOptions } from "signcode-tf" import { ElectronPackagerOptions } from "electron-packager-tf" //noinspection JSUnusedLocalSymbols const __awaiter = require("./awaiter") +interface FileCodeSigningInfo { + readonly file: string + readonly password?: string | null +} + export class WinPackager extends PlatformPackager { - certFilePromise: Promise + private readonly cscInfo: Promise - readonly iconPath: Promise + private readonly iconPath: Promise constructor(info: BuildInfo, cleanupTasks: Array<() => Promise>) { super(info) - if (this.options.cscLink != null && this.options.cscKeyPassword != null) { - this.certFilePromise = downloadCertificate(this.options.cscLink) + const certificateFile = this.customBuildOptions.certificateFile + if (certificateFile != null) { + const certificatePassword = this.customBuildOptions.certificatePassword + this.cscInfo = BluebirdPromise.resolve({ + file: certificateFile, + password: certificatePassword == null ? null : certificatePassword.trim(), + }) + } + else if (this.options.cscLink != null) { + this.cscInfo = downloadCertificate(this.options.cscLink) .then(path => { cleanupTasks.push(() => deleteFile(path, true)) - return path + return { + file: path, + password: this.getCscPassword(), + } }) } else { - this.certFilePromise = BluebirdPromise.resolve(null) + this.cscInfo = BluebirdPromise.resolve(null) } this.iconPath = this.getValidIconPath() @@ -78,15 +94,18 @@ export class WinPackager extends PlatformPackager { protected async doPack(options: ElectronPackagerOptions, outDir: string, appOutDir: string, arch: Arch, customBuildOptions: WinBuildOptions) { await super.doPack(options, outDir, appOutDir, arch, customBuildOptions) + await this.sign(appOutDir) + } - const cert = await this.certFilePromise - if (cert != null) { + protected async sign(appOutDir: string) { + const cscInfo = await this.cscInfo + if (cscInfo != null) { const filename = `${this.appName}.exe` - log(`Signing ${filename}`) - await BluebirdPromise.promisify(sign)({ + log(`Signing ${filename} (certificate file "${cscInfo.file}")`) + await this.doSign({ path: path.join(appOutDir, filename), - cert: cert, - password: this.options.cscKeyPassword!, + cert: cscInfo.file, + password: cscInfo.password!, name: this.appName, site: await this.computePackageUrl(), overwrite: true, @@ -95,6 +114,10 @@ export class WinPackager extends PlatformPackager { } } + protected async doSign(opts: SignOptions): Promise { + return BluebirdPromise.promisify(sign)(opts) + } + protected async computeEffectiveDistOptions(appOutDir: string, installerOutDir: string, packOptions: ElectronPackagerOptions, setupExeName: string): Promise { let iconUrl = this.customBuildOptions.iconUrl || this.devMetadata.build.iconUrl if (iconUrl == null) { @@ -120,6 +143,7 @@ export class WinPackager extends PlatformPackager { } rceditOptions["version-string"]!.LegalCopyright = packOptions["app-copyright"] + const cscInfo = await this.cscInfo const options: any = Object.assign({ name: this.metadata.name, productName: this.appName, @@ -133,8 +157,8 @@ export class WinPackager extends PlatformPackager { authors: this.metadata.author.name, iconUrl: iconUrl, setupIcon: await this.iconPath, - certificateFile: await this.certFilePromise, - certificatePassword: this.options.cscKeyPassword, + certificateFile: cscInfo == null ? null : cscInfo.file, + certificatePassword: cscInfo == null ? null : cscInfo.password, fixUpPaths: false, skipUpdateIcon: true, usePackageJson: false, diff --git a/test/src/globTest.ts b/test/src/globTest.ts index b4ba2e085a3..56fbc96e218 100644 --- a/test/src/globTest.ts +++ b/test/src/globTest.ts @@ -24,7 +24,7 @@ test.ifDevOrLinuxCi("ignore build resources", () => { return outputFile(path.join(projectDir, "one/build/foo.txt"), "data") }, packed: projectDir => { - return assertThat(path.join(projectDir, "dist", "linux", "resources", "app", "one", "build", "foo.txt")).isFile() + return assertThat(path.join(projectDir, outDirName, "linux", "resources", "app", "one", "build", "foo.txt")).isFile() }, }) }) diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 00f49d7246c..2ee8dac9269 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -41,7 +41,7 @@ export async function assertPack(fixtureName: string, packagerOptions: PackagerO const customTmpDir = process.env.TEST_APP_TMP_DIR if (useTempDir) { // non-osx test uses the same dir as osx test, but we cannot share node_modules (because tests executed in parallel) - const dir = customTmpDir == null ? path.join(tmpdir(), `${getTempName("electron-builder-test")}-${fixtureName}}`) : path.resolve(customTmpDir) + const dir = customTmpDir == null ? path.join(tmpdir(), `${getTempName("electron-builder-test")}`) : path.resolve(customTmpDir) if (customTmpDir != null) { console.log("Custom temp dir used: %s", customTmpDir) } diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts index 7f8b2379df6..a7a1e4c9ede 100755 --- a/test/src/winPackagerTest.ts +++ b/test/src/winPackagerTest.ts @@ -1,14 +1,13 @@ -import { Platform } from "out" +import { Platform, Arch, BuildInfo } from "out" import test from "./helpers/avaEx" import { assertPack, platform, modifyPackageJson, signed } from "./helpers/packTester" import { move, outputFile } from "fs-extra-p" import * as path from "path" import { WinPackager, computeDistOut } from "out/winPackager" -import { BuildInfo } from "out/platformPackager" import { Promise as BluebirdPromise } from "bluebird" -import * as assertThat from "should/as-function" import { ElectronPackagerOptions } from "electron-packager-tf" -import { Arch } from "out" +import { assertThat } from "./helpers/fileAssert" +import { SignOptions } from "signcode-tf" //noinspection JSUnusedLocalSymbols const __awaiter = require("out/awaiter") @@ -63,22 +62,20 @@ test.ifNotCiOsx("msi as string", t => t.throws(assertPack("test-app-one", platfo }), `msi expected to be boolean value, but string '"false"' was specified`) ) -test("detect install-spinner", () => { +test("detect install-spinner, certificateFile/password", () => { let platformPackager: CheckingWinPackager = null let loadingGifPath: string = null - // todo all PackagerOptions should be optional otherwise it is not possible to pass only several to override dev package.json - const devMetadata: any = { - build: { - win: { - certificatePassword: "pass", - } - } - } return assertPack("test-app-one", { targets: Platform.WINDOWS.createTarget(), platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingWinPackager(packager, cleanupTasks), - devMetadata: devMetadata + devMetadata: { + build: { + win: { + certificatePassword: "pass", + } + } + } }, { tempDirCreated: it => { loadingGifPath = path.join(it, "build", "install-spinner.gif") @@ -92,8 +89,9 @@ test("detect install-spinner", () => { })]) }, packed: () => { - assertThat(platformPackager.effectiveDistOptions.loadingGif).equal(loadingGifPath) - assertThat(platformPackager.effectiveDistOptions.certificateFile).equal("secretFile") + assertThat(platformPackager.effectiveDistOptions.loadingGif).isEqualTo(loadingGifPath) + assertThat(platformPackager.signOptions.cert).isEqualTo("secretFile") + assertThat(platformPackager.signOptions.password).isEqualTo("pass") return BluebirdPromise.resolve(null) }, }) @@ -109,6 +107,7 @@ test.ifNotCiOsx("icon not an image", (t: any) => t.throws(assertPack("test-app-o class CheckingWinPackager extends WinPackager { effectiveDistOptions: any + signOptions: SignOptions | null constructor(info: BuildInfo, cleanupTasks: Array<() => Promise>) { super(info, cleanupTasks) @@ -120,9 +119,16 @@ class CheckingWinPackager extends WinPackager { const appOutDir = this.computeAppOutDir(outDir, arch) const packOptions = this.computePackOptions(outDir, appOutDir, arch) this.effectiveDistOptions = await this.computeEffectiveDistOptions(appOutDir, installerOutDir, packOptions, "Foo.exe") + + await this.sign(appOutDir) } async packageInDistributableFormat(appOutDir: string, installerOutDir: string, arch: Arch, packOptions: ElectronPackagerOptions): Promise { // skip } + + protected doSign(opts: SignOptions): Promise { + this.signOptions = opts + return BluebirdPromise.resolve(null) + } } \ No newline at end of file