Repository: electron-userland/electron-forge (ts)

Issue:
Add a way to config publish targets for different make targets
Basically when #116 and #117 are sorted we will need a way to apply publish targets to different make targets through config.

**Current Technique:**

```bash
electron-forge publish --target=bintray
```

Now that isn't too bad but I think we can do better 😄 

**Proposal:**

```js
{
  "publish_targets": {
    "win32": ["github", "s3"],
    "darwin": ["github"],
    "linux": ["github", "bintray"]
  }
}
```

Pull request description:
feat(publisher): allow platform level config for publish targets
Also allows for multiple publish targets to be run simultaneously

Fixes #118

**Have you read the [section in CONTRIBUTING.md about pull requests](https://github.com/electron-userland/electron-forge/blob/master/CONTRIBUTING.md#filing-pull-requests)?**

Yes

**Are your changes appropriately documented?**

No 😆 (will do soon)

**Do your changes have sufficient test coverage?**

Yes

**Does the testsuite pass successfully on your local machine?**

Yes

**Summarize your changes:**




Patch:
diff --git a/src/api/publish.js b/src/api/publish.js
index cf15704fd..38999972b 100644
--- a/src/api/publish.js
+++ b/src/api/publish.js
@@ -1,6 +1,7 @@
 import 'colors';
 
 import asyncOra from '../util/ora-handler';
+import deprecate from '../util/deprecate';
 import getForgeConfig from '../util/forge-config';
 import readPackageJSON from '../util/read-package-json';
 import requireSearch from '../util/require-search';
@@ -31,8 +32,11 @@ export default async (providedOptions = {}) => {
     interactive: false,
     tag: null,
     makeOptions: {},
-    target: 'github',
+    target: null,
   }, providedOptions);
+  asyncOra.interactive = interactive;
+  // FIXME(MarshallOfSound): Change the method param to publishTargets in the next major bump
+  let publishTargets = target;
 
   const makeResults = await make(Object.assign({
     dir,
@@ -53,16 +57,26 @@ export default async (providedOptions = {}) => {
 
   const forgeConfig = await getForgeConfig(dir);
 
-  let publisher;
-  await asyncOra(`Resolving publish target: ${`${target}`.cyan}`, async () => {
-    publisher = requireSearch(__dirname, [
-      `../publishers/${target}.js`,
-      `electron-forge-publisher-${target}`,
-    ]);
-    if (!publisher) {
-      throw `Could not find a publish target with the name: ${target}`;
-    }
-  });
+  if (publishTargets === null) {
+    publishTargets = forgeConfig.publish_targets[makeOptions.platform || process.platform];
+  } else if (typeof publishTargets === 'string') {
+    // FIXME(MarshallOfSound): Remove this fallback string typeof check in the next major bump
+    deprecate('publish target as a string').replaceWith('an array of publish targets');
+    publishTargets = [publishTargets];
+  }
+
+  for (const publishTarget of publishTargets) {
+    let publisher;
+    await asyncOra(`Resolving publish target: ${`${publishTarget}`.cyan}`, async () => {
+      publisher = requireSearch(__dirname, [
+        `../publishers/${publishTarget}.js`,
+        `electron-forge-publisher-${publishTarget}`,
+      ]);
+      if (!publisher) {
+        throw `Could not find a publish target with the name: ${publishTarget}`;
+      }
+    });
 
-  await publisher(artifacts, packageJSON, forgeConfig, authToken, tag);
+    await publisher(artifacts, packageJSON, forgeConfig, authToken, tag);
+  }
 };
diff --git a/src/electron-forge-publish.js b/src/electron-forge-publish.js
index 1a8747211..926120c62 100644
--- a/src/electron-forge-publish.js
+++ b/src/electron-forge-publish.js
@@ -31,7 +31,7 @@ import { getMakeOptions } from './electron-forge-make';
     authToken: program.authToken,
     tag: program.tag,
   };
-  if (program.target) publishOpts.target = program.target;
+  if (program.target) publishOpts.target = program.target.split(',');
 
   publishOpts.makeOptions = getMakeOptions();
 
diff --git a/src/init/init-npm.js b/src/init/init-npm.js
index b9e27c51a..480a88f8c 100644
--- a/src/init/init-npm.js
+++ b/src/init/init-npm.js
@@ -14,7 +14,7 @@ export const devDeps = ['babel-preset-env', 'babel-preset-react', 'babel-plugin-
 export const exactDevDeps = ['electron-prebuilt-compile'];
 export const standardDeps = ['standard'];
 export const airbnDeps = ['eslint', 'eslint-config-airbnb', 'eslint-plugin-import',
-  'eslint-plugin-jsx-a11y', 'eslint-plugin-react'];
+  'eslint-plugin-jsx-a11y@^3.0.0', 'eslint-plugin-react'];
 
 export default async (dir, lintStyle) => {
   await asyncOra('Initializing NPM Module', async () => {
diff --git a/src/util/deprecate.js b/src/util/deprecate.js
new file mode 100644
index 000000000..75a5a6ce3
--- /dev/null
+++ b/src/util/deprecate.js
@@ -0,0 +1,8 @@
+import 'colors';
+import logSymbols from 'log-symbols';
+
+export default what => ({
+  replaceWith: (replacement) => {
+    console.warn(logSymbols.warning, `WARNING: ${what} is deprecated, please use ${replacement} instead`.yellow);
+  },
+});
diff --git a/src/util/forge-config.js b/src/util/forge-config.js
index ad35e3adf..ee96732f0 100644
--- a/src/util/forge-config.js
+++ b/src/util/forge-config.js
@@ -18,6 +18,7 @@ export default async (dir) => {
   }
   forgeConfig = Object.assign({
     make_targets: {},
+    publish_targets: {},
     electronPackagerConfig: {},
     electronWinstallerConfig: {},
     electronInstallerDebian: {},
@@ -30,6 +31,12 @@ export default async (dir) => {
     mas: ['zip'],
     linux: ['deb', 'rpm'],
   }, forgeConfig.make_targets);
+  forgeConfig.publish_targets = Object.assign({
+    win32: ['github'],
+    darwin: ['github'],
+    mas: ['github'],
+    linux: ['github'],
+  }, forgeConfig.publish_targets);
 
   const templateObj = Object.assign({}, packageJSON, { year: (new Date()).getFullYear() });
   const template = (obj) => {


Test patch:
diff --git a/test/fast/forge-config_spec.js b/test/fast/forge-config_spec.js
index b9a0c85e7..d039d3610 100644
--- a/test/fast/forge-config_spec.js
+++ b/test/fast/forge-config_spec.js
@@ -15,6 +15,12 @@ const defaults = {
   electronWinstallerConfig: {},
   electronInstallerDebian: {},
   electronInstallerRedhat: {},
+  publish_targets: {
+    win32: ['github'],
+    darwin: ['github'],
+    linux: ['github'],
+    mas: ['github'],
+  },
 };
 
 describe('forge-config', () => {
@@ -22,6 +28,10 @@ describe('forge-config', () => {
     expect(await findConfig(path.resolve(__dirname, '../fixture/dummy_app'))).to.be.deep.equal(Object.assign({}, defaults, {
       electronWinstallerConfig: { windows: 'magic' },
       windowsStoreConfig: { packageName: 'test' },
+      github_repository: {
+        name: 'project',
+        owner: 'dummy',
+      },
     }));
   });
 
diff --git a/test/fast/publish_spec.js b/test/fast/publish_spec.js
new file mode 100644
index 000000000..6351dec7a
--- /dev/null
+++ b/test/fast/publish_spec.js
@@ -0,0 +1,89 @@
+import chai, { expect } from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import path from 'path';
+import proxyquire from 'proxyquire';
+import sinon from 'sinon';
+
+chai.use(chaiAsPromised);
+
+describe('publish', () => {
+  let publish;
+  let makeStub;
+  let requireSearchStub;
+  let resolveStub;
+  let publisherSpy;
+
+  beforeEach(() => {
+    requireSearchStub = sinon.stub();
+    resolveStub = sinon.stub();
+    makeStub = sinon.stub();
+    publisherSpy = sinon.stub();
+
+    publish = proxyquire.noCallThru().load('../../src/api/publish', {
+      './make': async (...args) => makeStub(...args),
+      '../util/resolve-dir': async dir => resolveStub(dir),
+      '../util/read-package-json': () => Promise.resolve(require('../fixture/dummy_app/package.json')),
+      '../util/require-search': requireSearchStub,
+    }).default;
+
+    publisherSpy.returns(Promise.resolve());
+    requireSearchStub.returns(publisherSpy);
+    resolveStub.returns(path.resolve(__dirname, '../fixture/dummy_app'));
+    makeStub.returns([]);
+  });
+
+  it('should should call make with makeOptions', async () => {
+    await publish({
+      dir: __dirname,
+      interactive: false,
+    });
+    expect(makeStub.callCount).to.equal(1);
+  });
+
+  it('should call the resolved publisher with the appropriate args', async () => {
+    makeStub.returns([['artifact1', 'artifact2']]);
+    await publish({
+      dir: __dirname,
+      interactive: false,
+      authToken: 'my_token',
+      tag: 'my_special_tag',
+    });
+    expect(publisherSpy.callCount).to.equal(1);
+    expect(publisherSpy.firstCall.args).to.deep.equal([
+      ['artifact1', 'artifact2'],
+      require('../fixture/dummy_app/package.json'),
+      await require('../../src/util/forge-config').default(path.resolve(__dirname, '../fixture/dummy_app')),
+      'my_token',
+      'my_special_tag',
+    ]);
+  });
+
+  it('should default to publishing to github', async () => {
+    await publish({
+      dir: __dirname,
+      interactive: false,
+    });
+    expect(requireSearchStub.firstCall.args[1][0]).to.equal('../publishers/github.js');
+  });
+
+  it('should resolve publishers when given a string name', async () => {
+    await publish({
+      dir: __dirname,
+      interactive: false,
+      target: 'void',
+    });
+    expect(requireSearchStub.firstCall.args[1][0]).to.equal('../publishers/void.js');
+  });
+
+  it('should resolve consecutive publishers when given an array of names', async () => {
+    await publish({
+      dir: __dirname,
+      interactive: false,
+      target: ['void', 'nowhere', 'black_hole', 'everywhere'],
+    });
+    expect(requireSearchStub.getCall(0).args[1][0]).to.equal('../publishers/void.js');
+    expect(requireSearchStub.getCall(1).args[1][0]).to.equal('../publishers/nowhere.js');
+    expect(requireSearchStub.getCall(2).args[1][0]).to.equal('../publishers/black_hole.js');
+    expect(requireSearchStub.getCall(3).args[1][0]).to.equal('../publishers/everywhere.js');
+  });
+});
diff --git a/test/slow/start_spec_slow.js b/test/fast/start_spec.js
similarity index 100%
rename from test/slow/start_spec_slow.js
rename to test/fast/start_spec.js
diff --git a/test/fixture/dummy_app/package.json b/test/fixture/dummy_app/package.json
index c458a52ed..6e533c7d9 100644
--- a/test/fixture/dummy_app/package.json
+++ b/test/fixture/dummy_app/package.json
@@ -21,7 +21,11 @@
       "electronInstallerRedhat": {},
       "electronInstallerDebian": {},
       "electronWinstallerConfig": { "windows": "magic" },
-      "windowsStoreConfig": { "packageName": "test" }
+      "windowsStoreConfig": { "packageName": "test" },
+      "github_repository": {
+        "owner": "dummy",
+        "name": "project"
+      }
     }
   },
   "devDependencies": {