diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml
index 5bfc91828c7..cda3f20b634 100644
--- a/.idea/dictionaries/develar.xml
+++ b/.idea/dictionaries/develar.xml
@@ -19,6 +19,7 @@
appimagekit
appimagetool
appleid
+ applicationfolder
appveyor
appx
appxmanifest
@@ -156,6 +157,7 @@
inetc
inno
insertmacro
+ installfolder
installmode
instdir
instfiles
@@ -298,6 +300,7 @@
srcfolder
subcommand
subsequence
+ targetdir
targetsize
templating
testapp
@@ -339,6 +342,7 @@
winedlloverrides
winemenubuilder
winstaller
+ wixobj
workflows
writev
xamarin
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 12b9fdabf14..3da18291cbc 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -2,6 +2,7 @@
+
diff --git a/package.json b/package.json
index 3143886a7fa..5349110bc39 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
"temp-file": "^2.0.3",
"tunnel-agent": "^0.6.0",
"update-notifier": "^2.3.0",
- "yargs": "^9.0.1"
+ "yargs": "^10.0.3"
},
"devDependencies": {
"@develar/gitbook": "3.2.10",
diff --git a/packages/builder-util-runtime/src/uuid.ts b/packages/builder-util-runtime/src/uuid.ts
index 7b5489573e1..93aaddcb896 100644
--- a/packages/builder-util-runtime/src/uuid.ts
+++ b/packages/builder-util-runtime/src/uuid.ts
@@ -1,10 +1,5 @@
import { createHash, randomBytes } from "crypto"
-// error codes
-const invalidNamespace =
- "options.namespace must be a string or a Buffer " +
- "containing a valid UUID, or a UUID object"
-
const invalidName =
"options.name must be either a string or a Buffer"
@@ -42,10 +37,8 @@ export class UUID {
private version: number
// from rfc4122#appendix-C
- static readonly DNS = new UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
static readonly URL = new UUID("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
- static readonly OID = new UUID("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
- static readonly X500 = new UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
+ static readonly OID = UUID.parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
constructor(uuid: Buffer | string) {
const check = UUID.check(uuid)
@@ -67,7 +60,7 @@ export class UUID {
return uuidTimeBased(randomHost)
}
- static v5(name: string | Buffer, namespace: string | Buffer | UUID) {
+ static v5(name: string | Buffer, namespace: Buffer) {
return uuidNamed(name, "sha1", 0x50, namespace)
}
@@ -250,24 +243,11 @@ function uuidTimeBased(nodeId: Buffer, encoding: UuidEncoding = UuidEncoding.ASC
}
// v3 + v5
-function uuidNamed(name: string | Buffer, hashMethod: string, version: number, namespace: string | Buffer | UUID, encoding: UuidEncoding = UuidEncoding.ASCII) {
+function uuidNamed(name: string | Buffer, hashMethod: string, version: number, namespace: Buffer, encoding: UuidEncoding = UuidEncoding.ASCII) {
const hash = createHash(hashMethod)
- if (typeof namespace === "string") {
- if (!UUID.check(namespace)) {
- throw new Error(invalidNamespace)
- }
- namespace = UUID.parse(namespace)
- }
- else if (namespace instanceof UUID) {
- namespace = namespace.toBuffer()
- }
- else if (!(Buffer.isBuffer(namespace)) || namespace.length !== 16) {
- throw new Error(invalidNamespace)
- }
-
const nameIsNotAString = typeof name !== "string"
- if (nameIsNotAString && !(Buffer.isBuffer(name))) {
+ if (nameIsNotAString && !Buffer.isBuffer(name)) {
throw new Error(invalidName)
}
@@ -303,75 +283,6 @@ function uuidNamed(name: string | Buffer, hashMethod: string, version: number, n
return result
}
-// v4
-// function uuidRandom(arg1, arg2) {
-//
-// const options = arg1 || {}
-// const callback = typeof arg1 === "function" ? arg1 : arg2
-//
-// const buffer = crypto.randomBytes(16)
-//
-// buffer[6] = (buffer[6] & 0x0f) | 0x40
-// buffer[8] = (buffer[8] & 0x3f) | 0x80
-//
-// let result
-// switch (options.encoding && options.encoding[0]) {
-// case "b":
-// case "B":
-// result = buffer
-// break
-// case "o":
-// case "U":
-// result = new UUID(buffer)
-// break
-// default:
-// result = byte2hex[buffer[0]] + byte2hex[buffer[1]] +
-// byte2hex[buffer[2]] + byte2hex[buffer[3]] + "-" +
-// byte2hex[buffer[4]] + byte2hex[buffer[5]] + "-" +
-// byte2hex[(buffer[6] & 0x0f) | 0x40] +
-// byte2hex[buffer[7]] + "-" +
-// byte2hex[(buffer[8] & 0x3f) | 0x80] +
-// byte2hex[buffer[9]] + "-" +
-// byte2hex[buffer[10]] + byte2hex[buffer[11]] +
-// byte2hex[buffer[12]] + byte2hex[buffer[13]] +
-// byte2hex[buffer[14]] + byte2hex[buffer[15]]
-// break
-// }
-// if (callback) {
-// setImmediate(function() {
-// callback(null, result)
-// })
-// } else {
-// return result
-// }
-// }
-
-// v4 fast
-// function uuidRandomFast() {
-//
-// const r1 = Math.random() * 0x100000000
-// const r2 = Math.random() * 0x100000000
-// const r3 = Math.random() * 0x100000000
-// const r4 = Math.random() * 0x100000000
-//
-// return byte2hex[ r1 & 0xff] +
-// byte2hex[ r1 >>> 8 & 0xff] +
-// byte2hex[ r1 >>> 16 & 0xff] +
-// byte2hex[ r1 >>> 24 & 0xff] + "-" +
-// byte2hex[ r2 & 0xff] +
-// byte2hex[ r2 >>> 8 & 0xff] + "-" +
-// byte2hex[(r2 >>> 16 & 0x0f) | 0x40] +
-// byte2hex[ r2 >>> 24 & 0xff] + "-" +
-// byte2hex[(r3 & 0x3f) | 0x80] +
-// byte2hex[ r3 >>> 8 & 0xff] + "-" +
-// byte2hex[ r3 >>> 16 & 0xff] +
-// byte2hex[ r3 >>> 24 & 0xff] +
-// byte2hex[ r4 & 0xff] +
-// byte2hex[ r4 >>> 8 & 0xff] +
-// byte2hex[ r4 >>> 16 & 0xff] +
-// byte2hex[ r1 >>> 24 & 0xff]
-// }
-
function stringify(buffer: Buffer) {
return byte2hex[buffer[0]] + byte2hex[buffer[1]] +
byte2hex[buffer[2]] + byte2hex[buffer[3]] + "-" +
diff --git a/packages/electron-builder/package.json b/packages/electron-builder/package.json
index 9abee60efbb..b9dc07c82b4 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.1.0",
"chromium-pickle-js": "^0.2.0",
"cuint": "^0.2.2",
"app-package-builder": "0.0.0-semantic-release",
@@ -75,7 +75,7 @@
"sanitize-filename": "^1.6.1",
"semver": "^5.4.1",
"update-notifier": "^2.3.0",
- "yargs": "^9.0.1",
+ "yargs": "^10.0.3",
"debug": "^3.1.0",
"asar-integrity": "0.0.0-semantic-release",
"lazy-val": "^1.0.2",
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 f6f8c6cb03d..73f4390a6df 100644
--- a/packages/electron-builder/src/cli/create-self-signed-cert.ts
+++ b/packages/electron-builder/src/cli/create-self-signed-cert.ts
@@ -4,7 +4,7 @@ import { bold } from "chalk"
import { ensureDir } from "fs-extra-p"
import * as path from "path"
import sanitizeFileName from "sanitize-filename"
-import { quoteString } from "../targets/appx"
+import { quoteString } from "../targets/AppxTarget"
import { getSignVendorPath } from "../windowsCodeSign"
/** @internal */
diff --git a/packages/electron-builder/src/configuration.ts b/packages/electron-builder/src/configuration.ts
index d63d2edf5a7..be0568507aa 100644
--- a/packages/electron-builder/src/configuration.ts
+++ b/packages/electron-builder/src/configuration.ts
@@ -5,9 +5,11 @@ import { DmgOptions, MacConfiguration, MasConfiguration, PkgOptions } from "./op
import { PlatformSpecificBuildOptions } from "./options/PlatformSpecificBuildOptions"
import { SnapOptions } from "./options/SnapOptions"
import { SquirrelWindowsOptions } from "./options/SquirrelWindowsOptions"
-import { AppXOptions, WindowsConfiguration } from "./options/winOptions"
+import { WindowsConfiguration } from "./options/winOptions"
import { PlatformPackager } from "./platformPackager"
import { NsisOptions, NsisWebOptions, PortableOptions } from "./targets/nsis/nsisOptions"
+import { MsiOptions } from "./options/MsiOptions"
+import { AppXOptions } from "./options/AppXOptions"
/**
* Configuration Options
@@ -58,6 +60,8 @@ export interface Configuration extends PlatformSpecificBuildOptions {
readonly nsisWeb?: NsisWebOptions | null
readonly portable?: PortableOptions | null
readonly appx?: AppXOptions | null
+ /** @private */
+ readonly msi?: MsiOptions | null
readonly squirrelWindows?: SquirrelWindowsOptions | null
/**
diff --git a/packages/electron-builder/src/index.ts b/packages/electron-builder/src/index.ts
index 23265d67b6e..17a64222d4a 100644
--- a/packages/electron-builder/src/index.ts
+++ b/packages/electron-builder/src/index.ts
@@ -7,7 +7,9 @@ export { Configuration, AfterPackContext, MetadataDirectories, Protocol, Release
export { PlatformSpecificBuildOptions, AsarOptions, FileSet } from "./options/PlatformSpecificBuildOptions"
export { FileAssociation } from "./options/FileAssociation"
export { MacConfiguration, DmgOptions, MasConfiguration, MacOsTargetName, PkgOptions, DmgContent, DmgWindow } from "./options/macOptions"
-export { WindowsConfiguration, AppXOptions } from "./options/winOptions"
+export { WindowsConfiguration } from "./options/winOptions"
+export { AppXOptions } from "./options/AppXOptions"
+export { MsiOptions } from "./options/MsiOptions"
export { NsisOptions, NsisWebOptions, PortableOptions, CommonNsisOptions } from "./targets/nsis/nsisOptions"
export { LinuxConfiguration, DebOptions, CommonLinuxOptions, LinuxTargetSpecificOptions, AppImageOptions } from "./options/linuxOptions"
export { SnapOptions } from "./options/SnapOptions"
diff --git a/packages/electron-builder/src/options/AppXOptions.ts b/packages/electron-builder/src/options/AppXOptions.ts
new file mode 100644
index 00000000000..682ac45c15b
--- /dev/null
+++ b/packages/electron-builder/src/options/AppXOptions.ts
@@ -0,0 +1,52 @@
+import { TargetSpecificOptions } from "../core"
+
+export interface AppXOptions extends TargetSpecificOptions {
+ /**
+ * The application id. Defaults to `identityName`. Can’t start with numbers.
+ */
+ readonly applicationId?: string
+
+ /**
+ * The background color of the app tile. See [Visual Elements](https://msdn.microsoft.com/en-us/library/windows/apps/br211471.aspx).
+ * @default #464646
+ */
+ readonly backgroundColor?: string | null
+
+ /**
+ * A friendly name that can be displayed to users. Corresponds to [Properties.DisplayName](https://msdn.microsoft.com/en-us/library/windows/apps/br211432.aspx).
+ * Defaults to the application product name.
+ */
+ readonly displayName?: string | null
+
+ /**
+ * The name. Corresponds to [Identity.Name](https://msdn.microsoft.com/en-us/library/windows/apps/br211441.aspx). Defaults to the [application name](/configuration/configuration#Metadata-name).
+ */
+ readonly identityName?: string | null
+
+ /**
+ * The Windows Store publisher. Not used if AppX is build for testing. See [AppX Package Code Signing](#appx-package-code-signing) below.
+ */
+ readonly publisher?: string | null
+
+ /**
+ * A friendly name for the publisher that can be displayed to users. Corresponds to [Properties.PublisherDisplayName](https://msdn.microsoft.com/en-us/library/windows/apps/br211460.aspx).
+ * Defaults to company name from the application metadata.
+ */
+ readonly publisherDisplayName?: string | null
+
+ /**
+ * The list of [supported languages](https://docs.microsoft.com/en-us/windows/uwp/globalizing/manage-language-and-region#specify-the-supported-languages-in-the-apps-manifest) that will be listed in the Windows Store.
+ * The first entry (index 0) will be the default language.
+ * Defaults to en-US if omitted.
+ */
+ readonly languages?: Array | string | null
+
+ /**
+ * @private
+ * @default false
+ */
+ readonly electronUpdaterAware?: boolean
+
+ /** @private */
+ readonly makeappxArgs?: Array | null
+}
\ No newline at end of file
diff --git a/packages/electron-builder/src/options/MsiOptions.ts b/packages/electron-builder/src/options/MsiOptions.ts
new file mode 100644
index 00000000000..8607ad923b9
--- /dev/null
+++ b/packages/electron-builder/src/options/MsiOptions.ts
@@ -0,0 +1,26 @@
+import { TargetSpecificOptions } from "../core"
+
+export interface MsiOptions extends TargetSpecificOptions {
+ /**
+ * One-click installation.
+ * @default true
+ */
+ readonly oneClick?: boolean
+
+ /***
+ * Install per all users (per-machine).
+ * @default true
+ */
+ readonly perMachine?: 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.
+ */
+ readonly upgradeCode?: string | null
+
+ /**
+ * If `warningsAsErrors` is `true` (default): treat warnings as errors. If `warningsAsErrors` is `false`: allow warnings.
+ * @default true
+ */
+ readonly warningsAsErrors?: boolean
+}
\ No newline at end of file
diff --git a/packages/electron-builder/src/options/winOptions.ts b/packages/electron-builder/src/options/winOptions.ts
index 1b765a6444b..202aaef123b 100644
--- a/packages/electron-builder/src/options/winOptions.ts
+++ b/packages/electron-builder/src/options/winOptions.ts
@@ -1,4 +1,4 @@
-import { PlatformSpecificBuildOptions, TargetConfigType, TargetSpecificOptions } from "../index"
+import { PlatformSpecificBuildOptions, TargetConfigType } from "../index"
import { CustomWindowsSign } from "../windowsCodeSign"
export interface WindowsConfiguration extends PlatformSpecificBuildOptions {
@@ -89,55 +89,4 @@ export interface WindowsConfiguration extends PlatformSpecificBuildOptions {
readonly requestedExecutionLevel?: RequestedExecutionLevel | null
}
-export type RequestedExecutionLevel = "asInvoker" | "highestAvailable" | "requireAdministrator"
-
-export interface AppXOptions extends TargetSpecificOptions {
- /**
- * The application id. Defaults to `identityName`. Can’t start with numbers.
- */
- readonly applicationId?: string
-
- /**
- * The background color of the app tile. See [Visual Elements](https://msdn.microsoft.com/en-us/library/windows/apps/br211471.aspx).
- * @default #464646
- */
- readonly backgroundColor?: string | null
-
- /**
- * A friendly name that can be displayed to users. Corresponds to [Properties.DisplayName](https://msdn.microsoft.com/en-us/library/windows/apps/br211432.aspx).
- * Defaults to the application product name.
- */
- readonly displayName?: string | null
-
- /**
- * The name. Corresponds to [Identity.Name](https://msdn.microsoft.com/en-us/library/windows/apps/br211441.aspx). Defaults to the [application name](/configuration/configuration#Metadata-name).
- */
- readonly identityName?: string | null
-
- /**
- * The Windows Store publisher. Not used if AppX is build for testing. See [AppX Package Code Signing](#appx-package-code-signing) below.
- */
- readonly publisher?: string | null
-
- /**
- * A friendly name for the publisher that can be displayed to users. Corresponds to [Properties.PublisherDisplayName](https://msdn.microsoft.com/en-us/library/windows/apps/br211460.aspx).
- * Defaults to company name from the application metadata.
- */
- readonly publisherDisplayName?: string | null
-
- /**
- * The list of [supported languages](https://docs.microsoft.com/en-us/windows/uwp/globalizing/manage-language-and-region#specify-the-supported-languages-in-the-apps-manifest) that will be listed in the Windows Store.
- * The first entry (index 0) will be the default language.
- * Defaults to en-US if omitted.
- */
- readonly languages?: Array | string | null
-
- /**
- * @private
- * @default false
- */
- readonly electronUpdaterAware?: boolean
-
- /** @private */
- readonly makeappxArgs?: Array | null
-}
+export type RequestedExecutionLevel = "asInvoker" | "highestAvailable" | "requireAdministrator"
\ No newline at end of file
diff --git a/packages/electron-builder/src/parallels.ts b/packages/electron-builder/src/parallels.ts
index 947510f0163..8f89e5173e2 100644
--- a/packages/electron-builder/src/parallels.ts
+++ b/packages/electron-builder/src/parallels.ts
@@ -132,7 +132,10 @@ class ParallelsVmManager extends VmManager {
}
export function macPathToParallelsWindows(file: string) {
- return "\\\\Mac\\Host\\\\" + file
+ if (file.startsWith("C:\\")) {
+ return file
+ }
+ return "\\\\Mac\\Host\\" + file.replace(/\//g, "\\")
}
export interface ParallelsVm {
diff --git a/packages/electron-builder/src/targets/appx.ts b/packages/electron-builder/src/targets/AppxTarget.ts
similarity index 92%
rename from packages/electron-builder/src/targets/appx.ts
rename to packages/electron-builder/src/targets/AppxTarget.ts
index 6648be2987e..b141a36150c 100644
--- a/packages/electron-builder/src/targets/appx.ts
+++ b/packages/electron-builder/src/targets/AppxTarget.ts
@@ -1,15 +1,16 @@
import BluebirdPromise from "bluebird-lst"
-import { Arch, asArray, log, debug } from "builder-util"
+import { Arch, asArray, log } from "builder-util"
import { walk, copyOrLinkFile } from "builder-util/out/fs"
-import { emptyDir, readdir, readFile, remove, writeFile } from "fs-extra-p"
+import { emptyDir, readdir, readFile, writeFile } from "fs-extra-p"
import * as path from "path"
import { deepAssign } from "read-config-file/out/deepAssign"
import { Target } from "../core"
-import { AppXOptions } from "../options/winOptions"
import { getTemplatePath } from "../util/pathManager"
import { getSignVendorPath, isOldWin6 } from "../windowsCodeSign"
import { WinPackager } from "../winPackager"
import { VmManager } from "../parallels"
+import { createHelperDir } from "./targetUtil"
+import { AppXOptions } from "../"
const APPX_ASSETS_DIR_NAME = "appx"
@@ -39,14 +40,9 @@ export default class AppXTarget extends Target {
const vendorPath = await getSignVendorPath()
const vm = await packager.vm.value
- const tempDir = path.join(this.outDir, `__appx-temp-${Arch[arch]}`)
- await emptyDir(tempDir)
+ const stageDir = await createHelperDir(this, arch)
- function getTempFile(name: string) {
- return path.join(tempDir, name)
- }
-
- const mappingFile = getTempFile("mapping.txt")
+ const mappingFile = stageDir.getTempFile("mapping.txt")
const artifactName = packager.expandArtifactNamePattern(this.options, "appx", arch)
const artifactPath = path.join(this.outDir, artifactName)
const makeAppXArgs = ["pack", "/o" /* overwrite the output file if it exists */,
@@ -70,16 +66,16 @@ export default class AppXTarget extends Target {
const assetInfo = await AppXTarget.computeUserAssets(vm, vendorPath, userAssetDir)
const userAssets = assetInfo.userAssets
- const manifestFile = getTempFile("AppxManifest.xml")
+ const manifestFile = stageDir.getTempFile("AppxManifest.xml")
await this.writeManifest(getTemplatePath("appx"), manifestFile, arch, await this.computePublisherName(), userAssets)
mappingList.push(assetInfo.mappings)
mappingList.push([`"${vm.toVmFile(manifestFile)}" "AppxManifest.xml"`])
if (isScaledAssetsProvided(userAssets)) {
- const outFile = vm.toVmFile(getTempFile("resources.pri"))
+ const outFile = vm.toVmFile(stageDir.getTempFile("resources.pri"))
const makePriPath = vm.toVmFile(path.join(vendorPath, "windows-10", Arch[arch], "makepri.exe"))
- const assetRoot = path.join(tempDir, "appx/assets")
+ const assetRoot = path.join(stageDir.tempDir, "appx/assets")
await emptyDir(assetRoot)
await BluebirdPromise.map(assetInfo.allAssets, it => copyOrLinkFile(it, path.join(assetRoot, path.basename(it))))
@@ -92,8 +88,8 @@ export default class AppXTarget extends Target {
])
// in addition to resources.pri, resources.scale-140.pri and other such files will be generated
- for (const resourceFile of (await readdir(tempDir)).filter(it => it.startsWith("resources.")).sort()) {
- mappingList.push([`"${vm.toVmFile(path.join(tempDir, resourceFile))}" "${resourceFile}"`])
+ for (const resourceFile of (await readdir(stageDir.tempDir)).filter(it => it.startsWith("resources.")).sort()) {
+ mappingList.push([`"${vm.toVmFile(path.join(stageDir.tempDir, resourceFile))}" "${resourceFile}"`])
}
makeAppXArgs.push("/l")
}
@@ -111,9 +107,7 @@ export default class AppXTarget extends Target {
await vm.exec(vm.toVmFile(path.join(vendorPath, "windows-10", Arch[arch], "makeappx.exe")), makeAppXArgs)
await packager.sign(artifactPath)
- if (!debug.enabled) {
- await remove(tempDir)
- }
+ await stageDir.cleanup()
packager.info.dispatchArtifactCreated({
file: artifactPath,
diff --git a/packages/electron-builder/src/targets/MsiTarget.ts b/packages/electron-builder/src/targets/MsiTarget.ts
new file mode 100644
index 00000000000..d91d4428c8f
--- /dev/null
+++ b/packages/electron-builder/src/targets/MsiTarget.ts
@@ -0,0 +1,222 @@
+import { Target } from "../core"
+import { WinPackager } from "../winPackager"
+import { Arch, warn } from "builder-util"
+import { readFile, writeFile } from "fs-extra-p"
+import { getTemplatePath } from "../util/pathManager"
+import * as path from "path"
+import { deepAssign } from "read-config-file/out/deepAssign"
+import { createHelperDir } from "./targetUtil"
+import { MsiOptions } from "../"
+import { UUID } from "builder-util-runtime"
+import BluebirdPromise from "bluebird-lst"
+import { walk } from "builder-util/out/fs"
+import { createHash } from "crypto"
+
+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"
+
+export default class MsiTarget extends Target {
+ readonly options: MsiOptions = deepAssign({
+ perMachine: true,
+ }, this.packager.platformSpecificBuildOptions, this.packager.config.msi)
+
+ constructor(private readonly packager: WinPackager, readonly outDir: string) {
+ super("msi")
+ }
+
+ async build(appOutDir: string, arch: Arch) {
+ const packager = this.packager
+ const vm = await packager.vm.value
+
+ const stageDir = await createHelperDir(this, arch)
+
+ const projectFile = stageDir.getTempFile("project.wxs")
+ const objectFile = stageDir.getTempFile("project.wixobj")
+ await writeFile(projectFile, await this.writeManifest(getTemplatePath("msi"), appOutDir, arch))
+
+ // const vendorPath = "/Users/develar/Library/Caches/electron-builder/wix"
+ const vendorPath = "C:\\Program Files (x86)\\WiX Toolset v4.0\\bin"
+
+ 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")
+ }
+ 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)
+
+ // 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")
+ }
+
+ 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 async writeManifest(templatePath: string, appOutDir: string, arch: Arch) {
+ 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
+ })
+
+ return (await readFile(path.join(templatePath, "template.wxs"), "utf8"))
+ .replace(/\$\{([a-zA-Z0-9]+)\}/g, (match, p1): string => {
+ const options = this.options
+ switch (p1) {
+ // wix in the name because special wix format can be used in the name
+ case "installationDirectoryWixName":
+ const name = /^[-_+0-9a-zA-Z ]+$/.test(appInfo.productFilename) ? appInfo.productFilename : appInfo.sanitizedName
+ if (options.perMachine) {
+ return name
+ }
+ return `LocalAppDataFolder:\\Programs\\${name}\\`
+
+ case "productName":
+ return appInfo.productName
+
+ case "manufacturer":
+ const companyName = appInfo.companyName
+ if (!companyName) {
+ warn(`Manufacturer is not set for MSI — please set "author" in the package.json`)
+ }
+ return companyName || appInfo.productName
+
+ case "upgradeCode":
+ return (options.upgradeCode || UUID.v5(appInfo.id, ELECTRON_BUILDER_UPGRADE_CODE_NS_UUID)).toUpperCase()
+
+ 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}`)
+
+ case "programFilesId":
+ if (options.perMachine) {
+ // https://stackoverflow.com/questions/1929038/compilation-error-ice80-the-64bitcomponent-uses-32bitdirectory
+ return arch === Arch.x64 ? "ProgramFiles64Folder" : "ProgramFilesFolder"
+ }
+ else {
+ return "LocalAppDataFolder"
+ }
+
+ default:
+ throw new Error(`Macro ${p1} is not defined`)
+ }
+ })
+ }
+}
+
+// function hashString(s: string) {
+// const hash = createHash("md5")
+// hash.update(s)
+// return hash.digest("hex")
+// }
+
+const nullByteBuffer = Buffer.from([0])
+
+function hashString2(s: string, s2: string) {
+ const hash = createHash("md5")
+ hash.update(s)
+ hash.update(nullByteBuffer)
+ hash.update(s2)
+ return hash.digest("hex")
+}
\ No newline at end of file
diff --git a/packages/electron-builder/src/targets/nsis/nsis.ts b/packages/electron-builder/src/targets/nsis/nsis.ts
index 016652bc968..64229f4070c 100644
--- a/packages/electron-builder/src/targets/nsis/nsis.ts
+++ b/packages/electron-builder/src/targets/nsis/nsis.ts
@@ -23,7 +23,7 @@ import { AppPackageHelper, NSIS_PATH, nsisTemplatesDir } from "./nsisUtil"
const debug = _debug("electron-builder:nsis")
// noinspection SpellCheckingInspection
-const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3"
+const ELECTRON_BUILDER_NS_UUID = UUID.parse("50e065bc-3134-11e6-9bab-38c9862bdaf3")
// noinspection SpellCheckingInspection
const nsisResourcePathPromise = new Lazy(() => getBinFromGithub("nsis-resources", "3.3.0", "4okc98BD0v9xDcSjhPVhAkBMqos+FvD/5/H72fTTIwoHTuWd2WdD7r+1j72hxd+ZXxq1y3FRW0x6Z3jR0VfpMw=="))
@@ -134,7 +134,7 @@ export class NsisTarget extends Target {
APP_GUID: guid,
PRODUCT_NAME: appInfo.productName,
PRODUCT_FILENAME: appInfo.productFilename,
- APP_FILENAME: (!oneClick || options.perMachine === true) && /^[-_+0-9a-zA-Z ]+$/.test(appInfo.productFilename) ? appInfo.productFilename : appInfo.name,
+ APP_FILENAME: (!oneClick || options.perMachine === true) && /^[-_+0-9a-zA-Z ]+$/.test(appInfo.productFilename) ? appInfo.productFilename : appInfo.sanitizedName,
APP_DESCRIPTION: appInfo.description,
VERSION: version,
diff --git a/packages/electron-builder/src/targets/targetUtil.ts b/packages/electron-builder/src/targets/targetUtil.ts
new file mode 100644
index 00000000000..ed982f0e687
--- /dev/null
+++ b/packages/electron-builder/src/targets/targetUtil.ts
@@ -0,0 +1,25 @@
+import { emptyDir, remove } from "fs-extra-p"
+import * as path from "path"
+import { Target } from "../core"
+import { Arch, debug } from "builder-util"
+
+export class StageDir {
+ constructor(readonly tempDir: string) {
+ }
+
+ getTempFile(name: string) {
+ return path.join(this.tempDir, name)
+ }
+
+ async cleanup() {
+ if (!debug.enabled) {
+ await remove(this.tempDir)
+ }
+ }
+}
+
+export async function createHelperDir(target: Target, arch: Arch): Promise {
+ const tempDir = path.join(target.outDir, `__${target.name}-temp-${Arch[arch]}`)
+ await emptyDir(tempDir)
+ return new StageDir(tempDir)
+}
\ No newline at end of file
diff --git a/packages/electron-builder/src/winPackager.ts b/packages/electron-builder/src/winPackager.ts
index b4d7ce6b88c..2f57cfe6d55 100644
--- a/packages/electron-builder/src/winPackager.ts
+++ b/packages/electron-builder/src/winPackager.ts
@@ -13,7 +13,7 @@ import { DIR_TARGET, Platform, Target } from "./core"
import { RequestedExecutionLevel, WindowsConfiguration } from "./options/winOptions"
import { Packager } from "./packager"
import { PlatformPackager } from "./platformPackager"
-import AppXTarget from "./targets/appx"
+import AppXTarget from "./targets/AppxTarget"
import { NsisTarget } from "./targets/nsis/nsis"
import { AppPackageHelper, CopyElevateHelper } from "./targets/nsis/nsisUtil"
import { WebInstallerTarget } from "./targets/nsis/WebInstallerTarget"
@@ -177,7 +177,10 @@ export class WinPackager extends PlatformPackager {
}
case "appx":
- return require("./targets/appx").default
+ return require("./targets/AppxTarget").default
+
+ case "msi":
+ return require("./targets/MsiTarget").default
default:
return null
diff --git a/packages/electron-builder/templates/msi/template.wxs b/packages/electron-builder/templates/msi/template.wxs
new file mode 100644
index 00000000000..dc97624899d
--- /dev/null
+++ b/packages/electron-builder/templates/msi/template.wxs
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ ${uiRef}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+${dirs}
+
+${files}
+
+
+
\ No newline at end of file
diff --git a/packages/electron-publish/package.json b/packages/electron-publish/package.json
index c1b46459862..3c1000ef78d 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.2.0"
+ "chalk": "2.1.0"
},
"typings": "./out/publisher.d.ts"
}
diff --git a/test/out/__snapshots__/configurationValidationTest.js.snap b/test/out/__snapshots__/configurationValidationTest.js.snap
index 0f4c0c8a97e..26335c064c7 100644
--- a/test/out/__snapshots__/configurationValidationTest.js.snap
+++ b/test/out/__snapshots__/configurationValidationTest.js.snap
@@ -20,7 +20,7 @@ Object {
exports[`validation 1`] = `
"Configuration is invalid.
- configuration has an unknown property 'foo'. These properties are valid:
- object { afterPack?, apk?, appId?, appImage?, appx?, artifactName?, asar?, asarUnpack?, beforeBuild?, buildDependenciesFromSource?, buildVersion?, compression?, copyright?, deb?, detectUpdateChannel?, directories?, dmg?, electronCompile?, electronDist?, electronDownload?, electronVersion?, extends?, extraFiles?, extraMetadata?, extraResources?, fileAssociations?, files?, forceCodeSigning?, freebsd?, generateUpdatesFilesForAllChannels?, icon?, linux?, mac?, mas?, muonVersion?, nodeGypRebuild?, npmArgs?, npmRebuild?, npmSkipBuildFromSource?, nsis?, nsisWeb?, p5p?, pacman?, pkg?, portable?, productName?, protocols?, publish?, releaseInfo?, rpm?, snap?, squirrelWindows?, target?, win? }
+ object { afterPack?, apk?, appId?, appImage?, appx?, artifactName?, asar?, asarUnpack?, beforeBuild?, buildDependenciesFromSource?, buildVersion?, compression?, copyright?, deb?, detectUpdateChannel?, directories?, dmg?, electronCompile?, electronDist?, electronDownload?, electronVersion?, extends?, extraFiles?, extraMetadata?, extraResources?, fileAssociations?, files?, forceCodeSigning?, freebsd?, generateUpdatesFilesForAllChannels?, icon?, linux?, mac?, mas?, msi?, muonVersion?, nodeGypRebuild?, npmArgs?, npmRebuild?, npmSkipBuildFromSource?, nsis?, nsisWeb?, p5p?, pacman?, pkg?, portable?, productName?, protocols?, publish?, releaseInfo?, rpm?, snap?, squirrelWindows?, target?, win? }
Configuration Options
- configuration.mac should be one of these:
diff --git a/test/out/windows/__snapshots__/msiTest.js.snap b/test/out/windows/__snapshots__/msiTest.js.snap
new file mode 100644
index 00000000000..b025ad70922
--- /dev/null
+++ b/test/out/windows/__snapshots__/msiTest.js.snap
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`msi 1`] = `
+Object {
+ "win": Array [
+ Object {
+ "arch": "x64",
+ "file": "Test App ßW-1.1.0.msi",
+ "safeArtifactName": "TestApp-1.1.0.msi",
+ },
+ ],
+}
+`;
diff --git a/test/src/windows/msiTest.ts b/test/src/windows/msiTest.ts
new file mode 100644
index 00000000000..6caf7fa86ab
--- /dev/null
+++ b/test/src/windows/msiTest.ts
@@ -0,0 +1,6 @@
+import { app } from "../helpers/packTester"
+import { Platform } from "electron-builder"
+
+test.ifAll.ifNotCi("msi", app({
+ targets: Platform.WINDOWS.createTarget("msi"),
+}))
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 0f29e82c0a9..552a9bffcb9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5232,6 +5232,29 @@ yargs-parser@^7.0.0:
dependencies:
camelcase "^4.1.0"
+yargs-parser@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.0.0.tgz#21d476330e5a82279a4b881345bf066102e219c6"
+ dependencies:
+ camelcase "^4.1.0"
+
+yargs@^10.0.3:
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.0.3.tgz#6542debd9080ad517ec5048fb454efe9e4d4aaae"
+ dependencies:
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ find-up "^2.1.0"
+ get-caller-file "^1.0.1"
+ os-locale "^2.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^8.0.0"
+
yargs@^3.32.0:
version "3.32.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"
@@ -5262,7 +5285,7 @@ yargs@^8.0.2:
y18n "^3.2.1"
yargs-parser "^7.0.0"
-yargs@^9.0.0, yargs@^9.0.1:
+yargs@^9.0.0:
version "9.0.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c"
dependencies: