diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml index cda3f20b634..a682717901d 100644 --- a/.idea/dictionaries/develar.xml +++ b/.idea/dictionaries/develar.xml @@ -26,6 +26,7 @@ archiver archs armv + arpproducticon asar aspx atexit @@ -83,6 +84,7 @@ docdash docstrap donorbox + dontnet dpkg dsym dyld @@ -259,6 +261,7 @@ prebuild precompilation preinstall + preinstalled prelease prerelease priconfig diff --git a/README.md b/README.md index 8492ba904b0..e0286cece3f 100644 --- a/README.md +++ b/README.md @@ -145,3 +145,4 @@ We do this open source work in our free time. If you'd like us to invest more ti ## Sponsors WorkFlowy +Tidepool diff --git a/appveyor.yml b/appveyor.yml index e09755e0505..7d093bca730 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ cache: - '%LOCALAPPDATA%\electron-builder\cache' environment: - TEST_FILES: ExtraBuildTest,BuildTest,extraMetadataTest,filesTest,globTest,nsisUpdaterTest,oneClickInstallerTest,installerTest,appxTest + TEST_FILES: ExtraBuildTest,BuildTest,extraMetadataTest,filesTest,globTest,nsisUpdaterTest,oneClickInstallerTest,installerTest,appxTest,msiTest install: - ps: Install-Product node 8 x64 diff --git a/package.json b/package.json index 5349110bc39..19d910fc057 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,9 @@ "7zip-bin": "^2.2.7", "archiver": "^2.1.0", "async-exit-hook": "^2.0.1", - "aws-sdk": "^2.138.0", + "aws-sdk": "^2.139.0", "bluebird-lst": "^1.0.5", - "chalk": "2.1.0", + "chalk": "^2.3.0", "chromium-pickle-js": "^0.2.0", "cuint": "^0.2.2", "debug": "^3.1.0", @@ -72,7 +72,6 @@ }, "devDependencies": { "@develar/gitbook": "3.2.10", - "@types/chalk": "^0.4.31", "@types/debug": "^0.0.30", "@types/ejs": "^2.3.33", "@types/electron-is-dev": "^0.3.0", diff --git a/packages/builder-util/package.json b/packages/builder-util/package.json index 016ff384e39..fac353b9935 100644 --- a/packages/builder-util/package.json +++ b/packages/builder-util/package.json @@ -16,7 +16,7 @@ "is-ci": "^1.0.10", "stat-mode": "^0.2.2", "bluebird-lst": "^1.0.5", - "chalk": "2.1.0", + "chalk": "^2.3.0", "debug": "^3.1.0", "node-emoji": "^1.8.1", "builder-util-runtime": "^0.0.0-semantic-release", diff --git a/packages/builder-util/src/log.ts b/packages/builder-util/src/log.ts index e1dbde01bf5..1f6700a67f5 100644 --- a/packages/builder-util/src/log.ts +++ b/packages/builder-util/src/log.ts @@ -1,5 +1,5 @@ import BluebirdPromise from "bluebird-lst" -import { yellow } from "chalk" +import chalk from "chalk" import { get as getEmoji } from "node-emoji" import WritableStream = NodeJS.WritableStream @@ -14,7 +14,7 @@ class Logger { } warn(message: string): void { - this.log(yellow(`Warning: ${message}`)) + this.log(chalk.yellow(`Warning: ${message}`)) } log(message: string): void { @@ -39,7 +39,7 @@ class TtyLogger extends Logger { } warn(message: string): void { - this.log(`${getEmoji("warning")} ${yellow(message)}`) + this.log(`${getEmoji("warning")} ${chalk.yellow(message)}`) } } diff --git a/packages/builder-util/src/promise.ts b/packages/builder-util/src/promise.ts index 17b50f915a6..c92e57042e0 100644 --- a/packages/builder-util/src/promise.ts +++ b/packages/builder-util/src/promise.ts @@ -1,7 +1,7 @@ -import { red } from "chalk" +import chalk from "chalk" export function printErrorAndExit(error: Error) { - console.error(red((error.stack || error).toString())) + console.error(chalk.red((error.stack || error).toString())) process.exit(-1) } diff --git a/packages/builder-util/src/util.ts b/packages/builder-util/src/util.ts index 5ee90646818..07ff3332356 100644 --- a/packages/builder-util/src/util.ts +++ b/packages/builder-util/src/util.ts @@ -1,6 +1,6 @@ import BluebirdPromise from "bluebird-lst" import { safeStringifyJson } from "builder-util-runtime" -import { red, yellow } from "chalk" +import chalk from "chalk" import { ChildProcess, execFile, spawn as _spawn, SpawnOptions } from "child_process" import { createHash } from "crypto" import _debug from "debug" @@ -99,18 +99,18 @@ export function exec(file: string, args?: Array | null, options?: ExecOp resolve(stdout.toString()) } else { - let message = red(removePassword(`Exit code: ${(error as any).code}. ${error.message}`)) + let message = chalk.red(removePassword(`Exit code: ${(error as any).code}. ${error.message}`)) if (stdout.length !== 0) { if (file.endsWith("wine")) { stdout = removeWineSpam(stdout.toString()) } - message += `\n${yellow(stdout.toString())}` + message += `\n${chalk.yellow(stdout.toString())}` } if (stderr.length !== 0) { if (file.endsWith("wine")) { stderr = removeWineSpam(stderr.toString()) } - message += `\n${red(stderr.toString())}` + message += `\n${chalk.red(stderr.toString())}` } reject(new Error(message)) diff --git a/packages/builder-util/src/wine.ts b/packages/builder-util/src/wine.ts index 8813c19db08..b8bb62f06c6 100644 --- a/packages/builder-util/src/wine.ts +++ b/packages/builder-util/src/wine.ts @@ -16,9 +16,9 @@ const wineExecutable = new Lazy(async () => { let version: string | null = null let checksum: string | null = null if (semver.gte(osVersion, "10.13.0")) { - version = "2.0.2-mac-10.13" + version = "2.0.3-mac-10.13" // noinspection SpellCheckingInspection - checksum = "v6r9RSQBAbfvpVQNrEj48X8Cw1181rEGMRatGxSKY5p+7khzzy/0tOdfHGO8cU+GqYvH43FAKMK8p6vUfCqSSA==" + checksum = "dlEVCf0YKP5IEiOKPNE48Q8NKXbXVdhuaI9hG2oyDEay2c+93PE5qls7XUbIYq4Xi1gRK8fkWeCtzN2oLpVQtg==" } else if (semver.gte(osVersion, "10.12.0")) { version = "2.0.1-mac-10.12" @@ -52,7 +52,16 @@ export function execWine(file: string, args: Array, options: ExecOptions } else { return wineExecutable.value - .then(wine => exec(wine.path, [file].concat(args), wine.env == null ? options : {env: wine.env, ...options})) + .then(wine => { + const effectiveOptions = wine.env == null ? options : {...options} + if (wine.env != null) { + effectiveOptions.env = options.env == null ? wine.env : { + ...options.env, + ...wine.env, + } + } + return exec(wine.path, [file].concat(args), effectiveOptions) + }) } } diff --git a/packages/electron-builder/package.json b/packages/electron-builder/package.json index b9dc07c82b4..52cc429d558 100644 --- a/packages/electron-builder/package.json +++ b/packages/electron-builder/package.json @@ -54,7 +54,7 @@ "7zip-bin": "^2.2.7", "async-exit-hook": "^2.0.1", "bluebird-lst": "^1.0.5", - "chalk": "2.1.0", + "chalk": "^2.3.0", "chromium-pickle-js": "^0.2.0", "cuint": "^0.2.2", "app-package-builder": "0.0.0-semantic-release", diff --git a/packages/electron-builder/src/appInfo.ts b/packages/electron-builder/src/appInfo.ts index 43faee1db7f..abe24d655c2 100644 --- a/packages/electron-builder/src/appInfo.ts +++ b/packages/electron-builder/src/appInfo.ts @@ -26,11 +26,8 @@ export class AppInfo { if (!isEmptyOrSpaces(this.buildNumber)) { buildVersion += `.${this.buildNumber}` } - this.buildVersion = buildVersion - } - else { - this.buildVersion = buildVersion } + this.buildVersion = buildVersion this.productName = info.config.productName || info.metadata.productName || info.metadata.name! this.productFilename = sanitizeFileName(this.productName) diff --git a/packages/electron-builder/src/builder.ts b/packages/electron-builder/src/builder.ts index 6c5573d1269..951d888f89f 100644 --- a/packages/electron-builder/src/builder.ts +++ b/packages/electron-builder/src/builder.ts @@ -2,7 +2,7 @@ import BluebirdPromise from "bluebird-lst" import { addValue, Arch, archFromString, isEmptyOrSpaces, warn } from "builder-util" import { CancellationToken } from "builder-util-runtime" import { executeFinally } from "builder-util/out/promise" -import { underline } from "chalk" +import chalk from "chalk" import { PublishOptions } from "electron-publish" import { deepAssign } from "read-config-file/out/deepAssign" import { Configuration } from "./configuration" @@ -304,19 +304,19 @@ export function configureBuildCommand(yargs: yargs.Yargs): yargs.Yargs { .option("mac", { group: buildGroup, alias: ["m", "o", "macos"], - description: `Build for macOS, accepts target list (see ${underline("https://goo.gl/5uHuzj")}).`, + description: `Build for macOS, accepts target list (see ${chalk.underline("https://goo.gl/5uHuzj")}).`, type: "array", }) .option("linux", { group: buildGroup, alias: "l", - description: `Build for Linux, accepts target list (see ${underline("https://goo.gl/4vwQad")})`, + description: `Build for Linux, accepts target list (see ${chalk.underline("https://goo.gl/4vwQad")})`, type: "array", }) .option("win", { group: buildGroup, alias: ["w", "windows"], - description: `Build for Windows, accepts target list (see ${underline("https://goo.gl/jYsTEJ")})`, + description: `Build for Windows, accepts target list (see ${chalk.underline("https://goo.gl/jYsTEJ")})`, type: "array", }) .option("x64", { @@ -342,7 +342,7 @@ export function configureBuildCommand(yargs: yargs.Yargs): yargs.Yargs { .option("publish", { group: publishGroup, alias: "p", - description: `Publish artifacts (to GitHub Releases), see ${underline("https://goo.gl/tSFycD")}`, + description: `Publish artifacts (to GitHub Releases), see ${chalk.underline("https://goo.gl/tSFycD")}`, choices: ["onTag", "onTagOrDraft", "always", "never", undefined as any], }) .option("draft", { @@ -385,7 +385,7 @@ export function configureBuildCommand(yargs: yargs.Yargs): yargs.Yargs { .option("config", { alias: ["c"], group: buildGroup, - description: "The path to an electron-builder config. Defaults to `electron-builder.yml` (or `json`, or `json5`), see " + underline("https://goo.gl/YFRJOM"), + description: "The path to an electron-builder config. Defaults to `electron-builder.yml` (or `json`, or `json5`), see " + chalk.underline("https://goo.gl/YFRJOM"), }) .group(["help", "version"], "Other:") .example("electron-builder -mwl", "build for macOS, Windows and Linux") diff --git a/packages/electron-builder/src/cli/cli.ts b/packages/electron-builder/src/cli/cli.ts index 95541ec4485..97572e20803 100644 --- a/packages/electron-builder/src/cli/cli.ts +++ b/packages/electron-builder/src/cli/cli.ts @@ -2,7 +2,7 @@ import { exec, log, warn } from "builder-util" import { printErrorAndExit } from "builder-util/out/promise" -import { cyan, dim, green, reset, underline } from "chalk" +import chalk from "chalk" import { readJson } from "fs-extra-p" import isCi from "is-ci" import * as path from "path" @@ -35,7 +35,7 @@ yargs yargs => yargs, wrap(argv => start())) .help() - .epilog(`See ${underline("https://electron.build")} for more documentation.`) + .epilog(`See ${chalk.underline("https://electron.build")} for more documentation.`) .strict() .recommendCommands() .argv @@ -63,7 +63,7 @@ function checkIsOutdated() { const notifier = updateNotifier({pkg: it}) if (notifier.update != null) { notifier.notify({ - message: `Update available ${dim(notifier.update.current)}${reset(" → ")}${green(notifier.update.latest)} \nRun ${cyan("yarn upgrade electron-builder")} to update` + message: `Update available ${chalk.dim(notifier.update.current)}${chalk.reset(" → ")}${chalk.green(notifier.update.latest)} \nRun ${chalk.cyan("yarn upgrade electron-builder")} to update` }) } }) diff --git a/packages/electron-builder/src/cli/create-self-signed-cert.ts b/packages/electron-builder/src/cli/create-self-signed-cert.ts index 73f4390a6df..e061f3cbbaf 100644 --- a/packages/electron-builder/src/cli/create-self-signed-cert.ts +++ b/packages/electron-builder/src/cli/create-self-signed-cert.ts @@ -1,6 +1,6 @@ import { exec, log, spawn, TmpDir } from "builder-util" import { unlinkIfExists } from "builder-util/out/fs" -import { bold } from "chalk" +import chalk from "chalk" import { ensureDir } from "fs-extra-p" import * as path from "path" import sanitizeFileName from "sanitize-filename" @@ -15,7 +15,7 @@ export async function createSelfSignedCert(publisher: string) { const cer = `${tempPrefix}.cer` const pvk = `${tempPrefix}.pvk` - log(bold('When asked to enter a password ("Create Private Key Password"), please select "None".')) + log(chalk.bold('When asked to enter a password ("Create Private Key Password"), please select "None".')) try { await ensureDir(path.dirname(tempPrefix)) diff --git a/packages/electron-builder/src/options/MsiOptions.ts b/packages/electron-builder/src/options/MsiOptions.ts index 8607ad923b9..d755220a52d 100644 --- a/packages/electron-builder/src/options/MsiOptions.ts +++ b/packages/electron-builder/src/options/MsiOptions.ts @@ -7,12 +7,19 @@ export interface MsiOptions extends TargetSpecificOptions { */ readonly oneClick?: boolean - /*** + /** * Install per all users (per-machine). * @default true + * @private Well, one-click per-user is not easy to write for us, feature hidden for now */ readonly perMachine?: boolean + /** + * *one-click installer only.* Whether to run the installed application after finish. + * @default true + */ + readonly runAfterFinish?: boolean + /** * The [upgrade code](https://msdn.microsoft.com/en-us/library/windows/desktop/aa372375(v=vs.85).aspx). Optional, by default generated using app id. */ @@ -23,4 +30,9 @@ export interface MsiOptions extends TargetSpecificOptions { * @default true */ readonly warningsAsErrors?: boolean + + /** + * The name that will be used for all shortcuts. Defaults to the application name. + */ + readonly shortcutName?: string | null } \ No newline at end of file diff --git a/packages/electron-builder/src/targets/AppxTarget.ts b/packages/electron-builder/src/targets/AppxTarget.ts index b141a36150c..80b259fc075 100644 --- a/packages/electron-builder/src/targets/AppxTarget.ts +++ b/packages/electron-builder/src/targets/AppxTarget.ts @@ -8,7 +8,7 @@ import { Target } from "../core" import { getTemplatePath } from "../util/pathManager" import { getSignVendorPath, isOldWin6 } from "../windowsCodeSign" import { WinPackager } from "../winPackager" -import { VmManager } from "../parallels" +import { VmManager } from "../vm/vm" import { createHelperDir } from "./targetUtil" import { AppXOptions } from "../" diff --git a/packages/electron-builder/src/targets/MsiTarget.ts b/packages/electron-builder/src/targets/MsiTarget.ts index d91d4428c8f..94fb34095eb 100644 --- a/packages/electron-builder/src/targets/MsiTarget.ts +++ b/packages/electron-builder/src/targets/MsiTarget.ts @@ -1,6 +1,6 @@ import { Target } from "../core" import { WinPackager } from "../winPackager" -import { Arch, warn } from "builder-util" +import { Arch, warn, isEmptyOrSpaces } from "builder-util" import { readFile, writeFile } from "fs-extra-p" import { getTemplatePath } from "../util/pathManager" import * as path from "path" @@ -11,11 +11,21 @@ import { UUID } from "builder-util-runtime" import BluebirdPromise from "bluebird-lst" import { walk } from "builder-util/out/fs" import { createHash } from "crypto" +import { VmManager } from "../vm/vm" +import { WineVmManager } from "../vm/wine" +import * as ejs from "ejs" +import { Lazy } from "lazy-val" +import { getBinFromGithub } from "builder-util/out/binDownload" const ELECTRON_BUILDER_UPGRADE_CODE_NS_UUID = UUID.parse("d752fe43-5d44-44d5-9fc9-6dd1bf19d5cc") const ELECTRON_BUILDER_COMPONENT_KEY_PATH_NS_UUID = UUID.parse("a1fd0bba-2e5e-48dd-8b0e-caa943b1b0c9") const ROOT_DIR_ID = "APPLICATIONFOLDER" +const projectTemplate = new Lazy<(data: any) => string>(async () => { + return ejs.compile(await readFile(path.join(getTemplatePath("msi"), "template.wxs"), "utf8")) +}) + +// WiX doesn't support Mono, so, dontnet462 is required to be installed for wine (preinstalled in our bundled wine) export default class MsiTarget extends Target { readonly options: MsiOptions = deepAssign({ perMachine: true, @@ -27,128 +37,92 @@ export default class MsiTarget extends Target { async build(appOutDir: string, arch: Arch) { const packager = this.packager - const vm = await packager.vm.value - const stageDir = await createHelperDir(this, arch) + const vm = process.platform === "win32" ? new VmManager() : new WineVmManager() const projectFile = stageDir.getTempFile("project.wxs") const objectFile = stageDir.getTempFile("project.wixobj") - await writeFile(projectFile, await this.writeManifest(getTemplatePath("msi"), appOutDir, arch)) + await writeFile(projectFile, await this.writeManifest(appOutDir, arch, vm)) - // const vendorPath = "/Users/develar/Library/Caches/electron-builder/wix" - const vendorPath = "C:\\Program Files (x86)\\WiX Toolset v4.0\\bin" + const vendorPath = await getBinFromGithub("wix", "4.0.0.5512", "cA62F/9OAuR7YnOORAhE0ZExUASD+PFtf67tckhX/GLXBvAwBk7c7+mx8Kwrt0TDomomVz1TaryUgcRz+zKXng==") + // noinspection SpellCheckingInspection const candleArgs = [ "-arch", arch === Arch.ia32 ? "x86" : (arch === Arch.armv7l ? "arm" : "x64"), "-out", vm.toVmFile(objectFile), - `-dappDir=${"C:\\Users\\develar\\win-unpacked"}`, - "-pedantic", - ] - if (this.options.warningsAsErrors !== false) { - candleArgs.push("-wx") - } + // `-dappDir=${"C:\\Users\\develar\\win-unpacked"}`, + `-dappDir=${vm.toVmFile(appOutDir)}`, + ].concat(this.getCommonWixArgs()) candleArgs.push(vm.toVmFile(projectFile)) await vm.exec(vm.toVmFile(path.join(vendorPath, "candle.exe")), candleArgs) const artifactName = packager.expandArtifactNamePattern(this.options, "msi", arch) const artifactPath = path.join(this.outDir, artifactName) + await this.light(objectFile, vm, artifactPath, appOutDir, vendorPath) + + await stageDir.cleanup() + + packager.info.dispatchArtifactCreated({ + file: artifactPath, + packager, + arch, + safeArtifactName: packager.computeSafeArtifactName(artifactName, "msi"), + target: this, + isWriteUpdateInfo: false, + }) + } + private async light(objectFile: string, vm: VmManager, artifactPath: string, appOutDir: string, vendorPath: string) { // noinspection SpellCheckingInspection const lightArgs = [ "-out", vm.toVmFile(artifactPath), - "-pedantic", "-v", // https://github.com/wixtoolset/issues/issues/5169 "-spdb", - `-dappDir=${"C:\\Users\\develar\\win-unpacked"}`, - // "-b", "Z:\\Volumes\\test\\electron-builder-test\\dist\\win-unpacked" || vm.toVmFile(appOutDir), - ] - if (this.options.warningsAsErrors !== false) { - lightArgs.push("-wx") + // https://sourceforge.net/p/wix/bugs/2405/ + // error LGHT1076 : ICE61: This product should remove only older versions of itself. The Maximum version is not less than the current product. (1.1.0.42 1.1.0.42) + "-sw1076", + // `-dappDir=${"C:\\Users\\develar\\win-unpacked"}`, + `-dappDir=${vm.toVmFile(appOutDir)}`, + // "-dcl:high", + ].concat(this.getCommonWixArgs()) + + // http://windows-installer-xml-wix-toolset.687559.n2.nabble.com/Build-3-5-2229-0-give-me-the-following-error-error-LGHT0216-An-unexpected-Win32-exception-with-errorn-td5707443.html + if (process.platform !== "win32") { + // noinspection SpellCheckingInspection + lightArgs.push("-sval") } if (this.options.oneClick === false) { - // lightArgs.push("-ext", vm.toVmFile(path.join(vendorPath, "WixUIExtension.dll"))) lightArgs.push("-ext", "WixUIExtension") } lightArgs.push(vm.toVmFile(objectFile)) await vm.exec(vm.toVmFile(path.join(vendorPath, "light.exe")), lightArgs) + } - await stageDir.cleanup() - - packager.info.dispatchArtifactCreated({ - file: artifactPath, - packager, - arch, - safeArtifactName: packager.computeSafeArtifactName(artifactName, "msi"), - target: this, - isWriteUpdateInfo: false, - }) + private getCommonWixArgs() { + const args: Array = ["-pedantic"] + if (this.options.warningsAsErrors !== false) { + args.push("-wx") + } + return args } - private async writeManifest(templatePath: string, appOutDir: string, arch: Arch) { + private async writeManifest(appOutDir: string, arch: Arch, vm: VmManager) { const appInfo = this.packager.appInfo - const registryKeyPathId = UUID.v5(appInfo.id, ELECTRON_BUILDER_COMPONENT_KEY_PATH_NS_UUID) - - const dirNames = new Set() - let dirs = "" - const fileSpace = " ".repeat(6) - - let isRootDirAddedToRemoveTable = false - - const files = await BluebirdPromise.map(walk(appOutDir), file => { - let packagePath = file.substring(appOutDir.length + 1) - if (path.sep !== "\\") { - packagePath = packagePath.replace(/\//g, "\\") - } - - let isAddRemoveFolder = false - - const lastSlash = packagePath.lastIndexOf("\\") - const fileName = lastSlash > 0 ? packagePath.substring(lastSlash + 1) : packagePath - let directoryId: string | null = null - let dirName = "" - // Wix Directory.FileSource doesn't work - https://stackoverflow.com/questions/21519388/wix-filesource-confusion - if (lastSlash > 0) { - // This Name attribute may also define multiple directories using the inline directory syntax. - // For example, "ProgramFilesFolder:\My Company\My Product\bin" would create a reference to a Directory element with Id="ProgramFilesFolder" then create directories named "My Company" then "My Product" then "bin" nested beneath each other. - // This syntax is a shortcut to defining each directory in an individual Directory element. - dirName = packagePath.substring(0, lastSlash) - // add U (user) suffix just to be sure that will be not overwrite system WIX directory ids. - directoryId = `${dirName.toLowerCase()}_u` - if (!dirNames.has(dirName)) { - isAddRemoveFolder = true - dirNames.add(dirName) - dirs += ` \n` - } - } - else if (!isRootDirAddedToRemoveTable) { - isRootDirAddedToRemoveTable = true - isAddRemoveFolder = true - } - - // since RegistryValue can be part of Component, *** *** *** *** *** *** *** *** *** wix cannot auto generate guid - // https://stackoverflow.com/questions/1405100/change-my-component-guid-in-wix - let result = `` - if (!this.options.perMachine) { - // https://stackoverflow.com/questions/16119708/component-testcomp-installs-to-user-profile-it-must-use-a-registry-key-under-hk - result += `\n${fileSpace} ` - if (isAddRemoveFolder) { - // https://stackoverflow.com/questions/3290576/directory-xx-is-in-the-user-profile-but-is-not-listed-in-the-removefile-table - result += `\n${fileSpace} ` - } - } - // Id="${hashString(packagePath)}" - result += `\n${fileSpace} \n${fileSpace}` - return result + const {files, dirs} = await this.computeFileDeclaration(appOutDir) + + const compression = this.packager.compression + const options = this.options + const text = (await projectTemplate.value)({ + isRunAfterFinish: options.runAfterFinish !== false, + isAssisted: options.oneClick === false, + iconPath: await this.packager.getIconPath(), + compressionLevel: compression === "store" ? "none" : "high", }) - return (await readFile(path.join(templatePath, "template.wxs"), "utf8")) + return text .replace(/\$\{([a-zA-Z0-9]+)\}/g, (match, p1): string => { const options = this.options switch (p1) { @@ -176,18 +150,11 @@ export default class MsiTarget extends Target { case "version": return appInfo.versionInWeirdWindowsForm - case "compressionLevel": - const compression = this.packager.compression - return compression === "store" ? "none" : "high" - - case "uiRef": - return options.oneClick === false ? '' : "" - case "dirs": return dirs case "files": - return fileSpace + files.join(`\n${fileSpace}`) + return files case "programFilesId": if (options.perMachine) { @@ -203,13 +170,87 @@ export default class MsiTarget extends Target { } }) } -} -// function hashString(s: string) { -// const hash = createHash("md5") -// hash.update(s) -// return hash.digest("hex") -// } + private async computeFileDeclaration(appOutDir: string) { + const appInfo = this.packager.appInfo + const registryKeyPathId = UUID.v5(appInfo.id, ELECTRON_BUILDER_COMPONENT_KEY_PATH_NS_UUID) + let isRootDirAddedToRemoveTable = false + const dirNames = new Set() + let dirs = "" + const fileSpace = " ".repeat(6) + + const files = await BluebirdPromise.map(walk(appOutDir), file => { + const packagePath = file.substring(appOutDir.length + 1) + let isAddRemoveFolder = false + + const lastSlash = packagePath.lastIndexOf(path.sep) + const fileName = lastSlash > 0 ? packagePath.substring(lastSlash + 1) : packagePath + let directoryId: string | null = null + let dirName = "" + // Wix Directory.FileSource doesn't work - https://stackoverflow.com/questions/21519388/wix-filesource-confusion + if (lastSlash > 0) { + // This Name attribute may also define multiple directories using the inline directory syntax. + // For example, "ProgramFilesFolder:\My Company\My Product\bin" would create a reference to a Directory element with Id="ProgramFilesFolder" then create directories named "My Company" then "My Product" then "bin" nested beneath each other. + // This syntax is a shortcut to defining each directory in an individual Directory element. + dirName = packagePath.substring(0, lastSlash) + // add U (user) suffix just to be sure that will be not overwrite system WIX directory ids. + directoryId = `${dirName.toLowerCase()}_u` + if (!dirNames.has(dirName)) { + isAddRemoveFolder = true + dirNames.add(dirName) + dirs += ` \n` + } + } + else if (!isRootDirAddedToRemoveTable) { + isRootDirAddedToRemoveTable = true + isAddRemoveFolder = true + } + + // since RegistryValue can be part of Component, *** *** *** *** *** *** *** *** *** wix cannot auto generate guid + // https://stackoverflow.com/questions/1405100/change-my-component-guid-in-wix + let result = `` + + // https://stackoverflow.com/questions/16119708/component-testcomp-installs-to-user-profile-it-must-use-a-registry-key-under-hk + result += `\n${fileSpace} ` + if (isAddRemoveFolder) { + // https://stackoverflow.com/questions/3290576/directory-xx-is-in-the-user-profile-but-is-not-listed-in-the-removefile-table + result += `\n${fileSpace} ` + } + } + else { + result += ">" + } + + result += `\n${fileSpace} \n` + const shortcutName = isEmptyOrSpaces(options.shortcutName) ? appInfo.productFilename : this.packager.expandMacro(options.shortcutName!!) + result += `${fileSpace} \n` + result += `${fileSpace}` + } + else { + result += `/>` + } + + return `${result}\n${fileSpace}` + }) + + return {dirs, files: fileSpace + files.join(`\n${fileSpace}`)} + } +} const nullByteBuffer = Buffer.from([0]) diff --git a/packages/electron-builder/src/targets/nsis/nsisOptions.ts b/packages/electron-builder/src/targets/nsis/nsisOptions.ts index b7b4d415a23..6d90d56b85b 100644 --- a/packages/electron-builder/src/targets/nsis/nsisOptions.ts +++ b/packages/electron-builder/src/targets/nsis/nsisOptions.ts @@ -53,7 +53,7 @@ export interface NsisOptions extends CommonNsisOptions, TargetSpecificOptions { readonly allowToChangeInstallationDirectory?: boolean /** - * *one-click installer only.* Run application after finish. + * *one-click installer only.* Whether to run the installed application after finish. * @default true */ readonly runAfterFinish?: boolean diff --git a/packages/electron-builder/src/vm/mono.ts b/packages/electron-builder/src/vm/mono.ts new file mode 100644 index 00000000000..3df20eb09fd --- /dev/null +++ b/packages/electron-builder/src/vm/mono.ts @@ -0,0 +1,30 @@ +import { SpawnOptions } from "child_process" +import { exec, ExecOptions, ExtraSpawnOptions, spawn } from "builder-util" +import { VmManager } from "./vm" + +export class MonoVmManager extends VmManager { + constructor(private readonly currentDirectory: string) { + super() + } + + exec(file: string, args: Array, options?: ExecOptions, isLogOutIfDebug = true): Promise { + return exec("mono", [file].concat(args), { + cwd: this.currentDirectory, + ...options, + }, isLogOutIfDebug) + } + + spawn(file: string, args: Array, options?: SpawnOptions, extraOptions?: ExtraSpawnOptions): Promise { + return spawn("mono", [file].concat(args), options, extraOptions) + } + + toVmFile(file: string): string { + const parentPathLengthWithSlash = this.currentDirectory.length + 1 + if (parentPathLengthWithSlash < file.length && file[this.currentDirectory.length] === "/" && file.startsWith(this.currentDirectory)) { + return file.substring(parentPathLengthWithSlash) + } + else { + return super.toVmFile(file) + } + } +} \ No newline at end of file diff --git a/packages/electron-builder/src/parallels.ts b/packages/electron-builder/src/vm/parallels.ts similarity index 71% rename from packages/electron-builder/src/parallels.ts rename to packages/electron-builder/src/vm/parallels.ts index 8f89e5173e2..8780b7745e2 100644 --- a/packages/electron-builder/src/parallels.ts +++ b/packages/electron-builder/src/vm/parallels.ts @@ -1,8 +1,9 @@ import { exec, spawn, ExecOptions, DebugLogger, ExtraSpawnOptions } from "builder-util" import { SpawnOptions, execFileSync } from "child_process" -import * as path from "path" +import { VmManager } from "./vm" -async function parseVmList(debugLogger: DebugLogger) { +/** @internal */ +export async function parseVmList(debugLogger: DebugLogger) { // do not log output if debug - it is huge, logged using debugLogger let rawList = await exec("prlctl", ["list", "-i", "-s", "name"], undefined, false) debugLogger.add("parallels.list", rawList) @@ -29,35 +30,8 @@ async function parseVmList(debugLogger: DebugLogger) { return result } -export async function getWindowsVm(debugLogger: DebugLogger): Promise { - const vmList = (await parseVmList(debugLogger)).filter(it => it.os === "win-10") - if (vmList.length === 0) { - throw new Error("Cannot find suitable Parallels Desktop virtual machine (Windows 10 is required)") - } - - // prefer running or suspended vm - return new ParallelsVmManager(vmList.find(it => it.state === "running") || vmList.find(it => it.state === "suspended") || vmList[0]) -} - -export class VmManager { - get pathSep(): string { - return path.sep - } - - exec(file: string, args: Array, options?: ExecOptions, isLogOutIfDebug = true): Promise { - return exec(file, args, options, isLogOutIfDebug) - } - - spawn(command: string, args: Array, options?: SpawnOptions, extraOptions?: ExtraSpawnOptions): Promise { - return spawn(command, args) - } - - toVmFile(file: string): string { - return file - } -} - -class ParallelsVmManager extends VmManager { +/** @internal */ +export class ParallelsVmManager extends VmManager { private startPromise: Promise private isExitHookAdded = false @@ -86,9 +60,9 @@ class ParallelsVmManager extends VmManager { .catch(error => this.handleExecuteError(error)) } - async spawn(command: string, args: Array, options?: SpawnOptions, extraOptions?: ExtraSpawnOptions): Promise { + async spawn(file: string, args: Array, options?: SpawnOptions, extraOptions?: ExtraSpawnOptions): Promise { await this.ensureThatVmStarted() - return await spawn("prlctl", ["exec", this.vm.id, command].concat(args), options, extraOptions) + return await spawn("prlctl", ["exec", this.vm.id, file].concat(args), options, extraOptions) .catch(error => this.handleExecuteError(error)) } @@ -135,7 +109,8 @@ export function macPathToParallelsWindows(file: string) { if (file.startsWith("C:\\")) { return file } - return "\\\\Mac\\Host\\" + file.replace(/\//g, "\\") + // return "\\\\Mac\\Host\\" + file.replace(/\//g, "\\") + return "Z:" + file.replace(/\//g, "\\") } export interface ParallelsVm { diff --git a/packages/electron-builder/src/vm/vm.ts b/packages/electron-builder/src/vm/vm.ts new file mode 100644 index 00000000000..7542284f174 --- /dev/null +++ b/packages/electron-builder/src/vm/vm.ts @@ -0,0 +1,32 @@ +import * as path from "path" +import { SpawnOptions } from "child_process" +import { DebugLogger, exec, ExecOptions, ExtraSpawnOptions, spawn } from "builder-util" +import { ParallelsVmManager, parseVmList } from "./parallels" + +export class VmManager { + get pathSep(): string { + return path.sep + } + + exec(file: string, args: Array, options?: ExecOptions, isLogOutIfDebug = true): Promise { + return exec(file, args, options, isLogOutIfDebug) + } + + spawn(file: string, args: Array, options?: SpawnOptions, extraOptions?: ExtraSpawnOptions): Promise { + return spawn(file, args, options, extraOptions) + } + + toVmFile(file: string): string { + return file + } +} + +export async function getWindowsVm(debugLogger: DebugLogger): Promise { + const vmList = (await parseVmList(debugLogger)).filter(it => it.os === "win-10") + if (vmList.length === 0) { + throw new Error("Cannot find suitable Parallels Desktop virtual machine (Windows 10 is required)") + } + + // prefer running or suspended vm + return new ParallelsVmManager(vmList.find(it => it.state === "running") || vmList.find(it => it.state === "suspended") || vmList[0]) +} \ No newline at end of file diff --git a/packages/electron-builder/src/vm/wine.ts b/packages/electron-builder/src/vm/wine.ts new file mode 100644 index 00000000000..8c7111c6d19 --- /dev/null +++ b/packages/electron-builder/src/vm/wine.ts @@ -0,0 +1,23 @@ +import { SpawnOptions } from "child_process" +import { ExecOptions, ExtraSpawnOptions, execWine } from "builder-util" +import { VmManager } from "./vm" +import * as path from "path" + +/** @internal */ +export class WineVmManager extends VmManager { + constructor() { + super() + } + + exec(file: string, args: Array, options?: ExecOptions, isLogOutIfDebug = true): Promise { + return execWine(file, args, options) + } + + spawn(file: string, args: Array, options?: SpawnOptions, extraOptions?: ExtraSpawnOptions): Promise { + throw new Error("Unsupported") + } + + toVmFile(file: string): string { + return path.win32.join("Z:", file) + } +} diff --git a/packages/electron-builder/src/winPackager.ts b/packages/electron-builder/src/winPackager.ts index 2f57cfe6d55..72981f75aa5 100644 --- a/packages/electron-builder/src/winPackager.ts +++ b/packages/electron-builder/src/winPackager.ts @@ -22,7 +22,7 @@ import { BuildCacheManager, digest } from "./util/cacheManager" import { isBuildCacheEnabled } from "./util/flags" import { time } from "./util/timer" import { CertificateFromStoreInfo, FileCodeSigningInfo, getCertificateFromStoreInfo, getSignVendorPath, sign, WindowsSignOptions } from "./windowsCodeSign" -import { VmManager, getWindowsVm } from "./parallels" +import { VmManager, getWindowsVm } from "./vm/vm" export class WinPackager extends PlatformPackager { readonly cscInfo = new Lazy(() => { diff --git a/packages/electron-builder/src/windowsCodeSign.ts b/packages/electron-builder/src/windowsCodeSign.ts index 6cb6b191705..97ec3fdb66f 100644 --- a/packages/electron-builder/src/windowsCodeSign.ts +++ b/packages/electron-builder/src/windowsCodeSign.ts @@ -8,7 +8,7 @@ import * as path from "path" import { WindowsConfiguration } from "./options/winOptions" import { resolveFunction } from "./platformPackager" import { isUseSystemSigncode } from "./util/flags" -import { VmManager } from "./parallels" +import { VmManager } from "./vm/vm" import { WinPackager } from "./winPackager" export function getSignVendorPath() { diff --git a/packages/electron-builder/templates/msi/template.wxs b/packages/electron-builder/templates/msi/template.wxs index dc97624899d..ab4a8b1d9e5 100644 --- a/packages/electron-builder/templates/msi/template.wxs +++ b/packages/electron-builder/templates/msi/template.wxs @@ -2,29 +2,56 @@ - + - ${uiRef} + = 601]]> - - + <% if (iconPath) { %> + + + <% } -%> + + <% if (isAssisted) { %> + + + + <% } -%> + + <% if (isRunAfterFinish) { %> + + + + + + <% } -%> + + + + - - + + + -${dirs} + ${dirs} -${files} + ${files} \ No newline at end of file diff --git a/packages/electron-publish/package.json b/packages/electron-publish/package.json index 3c1000ef78d..c7753bfb873 100644 --- a/packages/electron-publish/package.json +++ b/packages/electron-publish/package.json @@ -16,7 +16,7 @@ "bluebird-lst": "^1.0.5", "builder-util-runtime": "^0.0.0-semantic-release", "builder-util": "^0.0.0-semantic-release", - "chalk": "2.1.0" + "chalk": "^2.3.0" }, "typings": "./out/publisher.d.ts" } diff --git a/packages/electron-publish/src/publisher.ts b/packages/electron-publish/src/publisher.ts index 48efe25c620..dceb3792a6d 100644 --- a/packages/electron-publish/src/publisher.ts +++ b/packages/electron-publish/src/publisher.ts @@ -1,6 +1,6 @@ import { Arch, log } from "builder-util" import { CancellationToken, ProgressCallbackTransform } from "builder-util-runtime" -import { green } from "chalk" +import chalk from "chalk" import { createReadStream, stat, Stats } from "fs-extra-p" import { ClientRequest } from "http" import { basename } from "path" @@ -47,7 +47,7 @@ export abstract class Publisher { return null } else { - return this.context.progress.createBar(`[:bar] :percent :etas | ${green(fileName)} to ${this.providerName}`, {total: size, ...progressBarOptions}) + return this.context.progress.createBar(`[:bar] :percent :etas | ${chalk.green(fileName)} to ${this.providerName}`, {total: size, ...progressBarOptions}) } } diff --git a/packages/electron-publisher-s3/package.json b/packages/electron-publisher-s3/package.json index 3f42a6707a8..0f80e28ea2b 100644 --- a/packages/electron-publisher-s3/package.json +++ b/packages/electron-publisher-s3/package.json @@ -12,7 +12,7 @@ ], "dependencies": { "fs-extra-p": "^4.4.4", - "aws-sdk": "^2.138.0", + "aws-sdk": "^2.139.0", "mime": "^2.0.3", "electron-publish": "~0.0.0-semantic-release", "builder-util": "^0.0.0-semantic-release", diff --git a/scripts/publish.sh b/scripts/publish.sh index a7e35696ccb..f6d915f8b7d 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -11,6 +11,9 @@ set -e cd _book +mkdir sponsor-logos +cp ../scripts/sponsor-logos/*.svg sponsor-logos/ + # do not use force push - netlify doesn't trigger deploy for forced push git clone --no-checkout --branch en --single-branch git@github.com:develar/generated-gitbook-electron-builder.git ./repo.tmp mv ./repo.tmp/.git ./ diff --git a/scripts/sponsor-logos/Tidepool_Logo_Light.svg b/scripts/sponsor-logos/Tidepool_Logo_Light.svg new file mode 100644 index 00000000000..c09080c2e90 --- /dev/null +++ b/scripts/sponsor-logos/Tidepool_Logo_Light.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + diff --git a/test/out/windows/__snapshots__/msiTest.js.snap b/test/out/windows/__snapshots__/msiTest.js.snap index b025ad70922..6c362a48d38 100644 --- a/test/out/windows/__snapshots__/msiTest.js.snap +++ b/test/out/windows/__snapshots__/msiTest.js.snap @@ -5,7 +5,7 @@ Object { "win": Array [ Object { "arch": "x64", - "file": "Test App ßW-1.1.0.msi", + "file": "Test MSI-1.1.0.msi", "safeArtifactName": "TestApp-1.1.0.msi", }, ], diff --git a/test/src/helpers/checkDeps.ts b/test/src/helpers/checkDeps.ts index 9fabda36d46..87a36723294 100644 --- a/test/src/helpers/checkDeps.ts +++ b/test/src/helpers/checkDeps.ts @@ -1,5 +1,5 @@ import BluebirdPromise from "bluebird-lst" -import { bold } from "chalk" +import chalk from "chalk" import depCheck, { DepCheckResult } from "depcheck" import { readdir, readJson } from "fs-extra-p" import * as path from "path" @@ -29,13 +29,13 @@ async function check(projectDir: string, devPackageData: any): Promise // console.log(result) if (result.dependencies.length > 0) { - console.error(`${bold(packageName)} Unused dependencies: ${JSON.stringify(result.dependencies, null, 2)}`) + console.error(`${chalk.bold(packageName)} Unused dependencies: ${JSON.stringify(result.dependencies, null, 2)}`) return false } const unusedDevDependencies = result.devDependencies.filter(it => !it.startsWith("@types/") && !knownUnusedDevDependencies.has(it)) if (unusedDevDependencies.length > 0) { - console.error(`${bold(packageName)} Unused devDependencies: ${JSON.stringify(unusedDevDependencies, null, 2)}`) + console.error(`${chalk.bold(packageName)} Unused devDependencies: ${JSON.stringify(unusedDevDependencies, null, 2)}`) return false } @@ -52,7 +52,7 @@ async function check(projectDir: string, devPackageData: any): Promise } if (Object.keys(result.missing).length > 0) { - console.error(`${bold(packageName)} Missing dependencies: ${JSON.stringify(result.missing, null, 2)}`) + console.error(`${chalk.bold(packageName)} Missing dependencies: ${JSON.stringify(result.missing, null, 2)}`) return false } @@ -69,7 +69,7 @@ async function check(projectDir: string, devPackageData: any): Promise for (const file of usages) { if (file.startsWith(path.join(projectDir, "src") + path.sep)) { - console.error(`${bold(packageName)} Dev dependency ${name} is used in the sources`) + console.error(`${chalk.bold(packageName)} Dev dependency ${name} is used in the sources`) return false } } diff --git a/test/src/windows/msiTest.ts b/test/src/windows/msiTest.ts index 6caf7fa86ab..bc7eb57ee91 100644 --- a/test/src/windows/msiTest.ts +++ b/test/src/windows/msiTest.ts @@ -1,6 +1,27 @@ import { app } from "../helpers/packTester" import { Platform } from "electron-builder" -test.ifAll.ifNotCi("msi", app({ +test.ifAll("msi", app({ targets: Platform.WINDOWS.createTarget("msi"), + config: { + extraMetadata: { + // version: "1.0.0", + }, + productName: "Test MSI", + } +})) + +test.ifAll("assisted", app({ + targets: Platform.WINDOWS.createTarget("msi"), + config: { + extraMetadata: { + // version: "1.0.0", + }, + productName: "Test MSI Assisted", + // test lzx (currently, doesn't work on wine) + compression: "maximum", + msi: { + oneClick: false, + }, + } })) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 552a9bffcb9..4d1fcad6504 100644 --- a/yarn.lock +++ b/yarn.lock @@ -75,10 +75,6 @@ tmp "^0.0.33" urijs "1.18.12" -"@types/chalk@^0.4.31": - version "0.4.31" - resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9" - "@types/debug@^0.0.30": version "0.0.30" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-0.0.30.tgz#dc1e40f7af3b9c815013a7860e6252f6352a84df" @@ -443,9 +439,9 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" -aws-sdk@^2.138.0: - version "2.138.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.138.0.tgz#acb923132b51fafe8a464aa757f65d61ac30bd77" +aws-sdk@^2.139.0: + version "2.139.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.139.0.tgz#5eb93d0c16fd12a5be9ddf5db053f0dad677237f" dependencies: buffer "4.9.1" crypto-browserify "1.0.9" @@ -904,14 +900,6 @@ chainsaw@~0.1.0: dependencies: traverse ">=0.3.0 <0.4" -chalk@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" - dependencies: - ansi-styles "^3.1.0" - escape-string-regexp "^1.0.5" - supports-color "^4.0.0" - chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -922,9 +910,9 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.1, chalk@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.2.0.tgz#477b3bf2f9b8fd5ca9e429747e37f724ee7af240" +chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" dependencies: ansi-styles "^3.1.0" escape-string-regexp "^1.0.5"