Repository: electron-userland/electron-forge
Pull request #378 (2017-12-01 00:16:16)

Add Electron Wix MSI support
<!--
Thanks for filing a pull request!
Please check off all of the steps as they are completed by replacing [ ] with [x].
-->

* [x] I have read the [contribution documentation](https://github.com/electron-userland/electron-forge/blob/master/CONTRIBUTING.md) for this project.
* [x] I agree to follow the [code of conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project follows, as appropriate.
* [x] The changes are appropriately documented (if applicable).
* [x] The changes have sufficient test coverage (if applicable).
* [x] The testsuite passes successfully on my local machine (if applicable).

**Summarize your changes:**




malept: > Add Wix support

😲 Someone didn't use `npm run commit` 😲 
malept: > Waiting on an answer about Node requirements

See: https://github.com/felixrieseberg/electron-wix-msi/pull/3
malept: `electron-wix-msi` works on Node 6 as of 1.3.0 :tada:

Patch:
diff --git a/.appveyor.yml b/.appveyor.yml
index 9be5ddd37..f65c2b553 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -15,6 +15,14 @@ install:
 - ps: Install-Product node $env:nodejs_version $env:platform
 - set PATH=%APPDATA%\npm;%PATH%
 - yarn
+- ps: |
+    $wixToolsetBinPath = ";C:\Program Files (x86)\WiX Toolset v3.11\bin;"
+    $env:PATH = $env:PATH + $wixToolsetBinPath
+    [Environment]::SetEnvironmentVariable("Path", $env:PATH, [EnvironmentVariableTarget]::Machine)
+    if ((Get-Command "light.exe" -ErrorAction SilentlyContinue) -eq $null)
+    {
+      Write-Host "Unable to find light.exe in your PATH"
+    }
 
 test_script:
 - node --version
diff --git a/README.md b/README.md
index 17e85814d..c648856ab 100644
--- a/README.md
+++ b/README.md
@@ -181,6 +181,7 @@ config object:
 | `zip`       | All                 | Zips your packaged application | None | Yes | `zip` on Darwin/Linux |
 | `squirrel`  | Windows             | Generates an installer and `.nupkg` files for Squirrel.Windows | [`electronWinstallerConfig`](https://github.com/electron/windows-installer#usage) | Yes |  |
 | `appx`      | Windows             | Generates a Windows Store package | [`windowsStoreConfig`](https://github.com/felixrieseberg/electron-windows-store#programmatic-usage) | No |  |
+| `wix`       | Windows             | Generates a traditional MSI file | [`electronWixMSIConfig`](https://github.com/felixrieseberg/electron-wix-msi#configuration) | No | [Wix Toolit](https://github.com/felixrieseberg/electron-wix-msi#prerequisites) |
 | `dmg`       | Darwin              | Generates a DMG file | [`electronInstallerDMG`](https://github.com/mongodb-js/electron-installer-dmg#api) | No |  |
 | `deb`       | Linux               | Generates a Debian package | [`electronInstallerDebian`](https://github.com/unindented/electron-installer-debian#options) | Yes | [`fakeroot` and `dpkg`](https://github.com/unindented/electron-installer-debian#requirements) |
 | `rpm`       | Linux               | Generates an RPM package | [`electronInstallerRedhat`](https://github.com/unindented/electron-installer-redhat#options) | Yes | [`rpm`](https://github.com/unindented/electron-installer-redhatn#requirements) |
diff --git a/package.json b/package.json
index d78ce659c..ac8564f1f 100644
--- a/package.json
+++ b/package.json
@@ -158,7 +158,8 @@
     "electron-installer-flatpak": "^0.8.0",
     "electron-installer-redhat": "^0.5.0",
     "electron-windows-store": "^0.12.0",
-    "electron-winstaller": "^2.5.0"
+    "electron-winstaller": "^2.5.0",
+    "electron-wix-msi": "^1.3.0"
   },
   "engines": {
     "node": ">= 6.0"
diff --git a/src/makers/win32/appx.js b/src/makers/win32/appx.js
index 5079d73c8..94bd9a61f 100644
--- a/src/makers/win32/appx.js
+++ b/src/makers/win32/appx.js
@@ -1,12 +1,12 @@
 import fs from 'fs';
 import path from 'path';
-import parseAuthor from 'parse-author';
+import resolveCommand from 'cross-spawn/lib/util/resolveCommand';
 import windowsStore from 'electron-windows-store';
 import { isValidPublisherName, makeCert } from 'electron-windows-store/lib/sign';
 
-import resolveCommand from 'cross-spawn/lib/util/resolveCommand';
-import { ensureDirectory } from '../../util/ensure-output';
 import configFn from '../../util/config-fn';
+import getNameFromAuthor from '../../util/author-name';
+import { ensureDirectory } from '../../util/ensure-output';
 
 // electron-windows-store doesn't set its 'os' field even though it only runs on
 // win32
@@ -48,22 +48,8 @@ export async function createDefaultCertificate(publisherName, { certFilePath, ce
   return await makeCert(makeCertOptions);
 }
 
-export function getDistinguishedNameFromAuthor(author) {
-  let publisher = author || '';
-
-  if (typeof publisher === 'string') {
-    publisher = parseAuthor(publisher);
-  }
-
-  if (typeof publisher.name === 'string') {
-    publisher = publisher.name;
-  }
-
-  if (typeof publisher !== 'string') {
-    publisher = '';
-  }
-
-  return `CN=${publisher}`;
+function getDistinguishedNameFromAuthor(author) {
+  return `CN=${getNameFromAuthor(author)}`;
 }
 
 export default async ({ dir, appName, targetArch, forgeConfig, packageJSON }) => {
diff --git a/src/makers/win32/wix.js b/src/makers/win32/wix.js
new file mode 100644
index 000000000..dfc575271
--- /dev/null
+++ b/src/makers/win32/wix.js
@@ -0,0 +1,31 @@
+import path from 'path';
+
+import { ensureDirectory } from '../../util/ensure-output';
+import getNameFromAuthor from '../../util/author-name';
+import configFn from '../../util/config-fn';
+import isInstalled from '../../util/is-installed';
+
+export const isSupportedOnCurrentPlatform = async () => isInstalled('electron-wix-msi');
+
+export default async ({ dir, appName, targetArch, forgeConfig, packageJSON }) => {
+  const { MSICreator } = require('electron-wix-msi');
+
+  const outPath = path.resolve(dir, `../make/wix/${targetArch}`);
+  await ensureDirectory(outPath);
+
+  const creator = new MSICreator(Object.assign({
+    description: packageJSON.description,
+    name: appName,
+    version: packageJSON.version,
+    manufacturer: getNameFromAuthor(packageJSON.author),
+    exe: `${appName}.exe`,
+  }, configFn(forgeConfig.electronWixMSIConfig, targetArch), {
+    appDirectory: dir,
+    outputDirectory: outPath,
+  }));
+
+  await creator.create();
+  const { msiFile } = await creator.compile();
+
+  return [msiFile];
+};
diff --git a/src/util/author-name.js b/src/util/author-name.js
new file mode 100644
index 000000000..944c6febb
--- /dev/null
+++ b/src/util/author-name.js
@@ -0,0 +1,19 @@
+import parseAuthor from 'parse-author';
+
+export default function getNameFromAuthor(author) {
+  let publisher = author || '';
+
+  if (typeof publisher === 'string') {
+    publisher = parseAuthor(publisher);
+  }
+
+  if (typeof publisher.name === 'string') {
+    publisher = publisher.name;
+  }
+
+  if (typeof publisher !== 'string') {
+    publisher = '';
+  }
+
+  return publisher;
+}


Test patch:
diff --git a/test/fast/author-name_spec.js b/test/fast/author-name_spec.js
new file mode 100644
index 000000000..177c86e77
--- /dev/null
+++ b/test/fast/author-name_spec.js
@@ -0,0 +1,30 @@
+import { expect } from 'chai';
+
+import getNameFromAuthor from '../../src/util/author-name';
+
+describe('author-name', () => {
+  describe('getNameFromAuthor', () => {
+    [{
+      author: 'First Last',
+      expectedReturnValue: 'First Last',
+    }, {
+      author: 'First Last <first.last@example.com>',
+      expectedReturnValue: 'First Last',
+    }, {
+      author: {
+        name: 'First Last',
+      },
+      expectedReturnValue: 'First Last',
+    }, {
+      author: undefined,
+      expectedReturnValue: '',
+    }, {
+      author: '',
+      expectedReturnValue: '',
+    }].forEach((scenario) => {
+      it(`${JSON.stringify(scenario.author)} -> "${scenario.expectedReturnValue}"`, () => {
+        expect(getNameFromAuthor(scenario.author)).to.equal(scenario.expectedReturnValue);
+      });
+    });
+  });
+});
diff --git a/test/fast/makers/appx_spec.js b/test/fast/makers/appx_spec.js
index a1062f6b7..e4154259f 100644
--- a/test/fast/makers/appx_spec.js
+++ b/test/fast/makers/appx_spec.js
@@ -3,7 +3,7 @@ import { join } from 'path';
 import fs from 'fs-extra';
 import { expect } from 'chai';
 
-import { getDistinguishedNameFromAuthor, createDefaultCertificate } from '../../../src/makers/win32/appx';
+import { createDefaultCertificate } from '../../../src/makers/win32/appx';
 
 describe('appx maker', () => {
   describe('createDefaultCertificate', () => {
@@ -35,29 +35,4 @@ describe('appx maker', () => {
       });
     }
   });
-
-  describe('getDistinguishedNameFromAuthor', () => {
-    [{
-      author: 'First Last',
-      expectedReturnValue: 'CN=First Last',
-    }, {
-      author: 'First Last <first.last@example.com>',
-      expectedReturnValue: 'CN=First Last',
-    }, {
-      author: {
-        name: 'First Last',
-      },
-      expectedReturnValue: 'CN=First Last',
-    }, {
-      author: undefined,
-      expectedReturnValue: 'CN=',
-    }, {
-      author: '',
-      expectedReturnValue: 'CN=',
-    }].forEach((scenario) => {
-      it(`${JSON.stringify(scenario.author)} -> "${scenario.expectedReturnValue}"`, () => {
-        expect(getDistinguishedNameFromAuthor(scenario.author)).to.equal(scenario.expectedReturnValue);
-      });
-    });
-  });
 });