Repository: ember-cli/ember-cli (js)

Issue:
Installing addons from blueprint fails: "Could not find addon"
When running `addAddonsToProject` in a blueprint:

```
installing ember-cli-zesty-app-base
  identical testem.js
  install addons ember-cli-eslint@^1.7.0, ember-cli-template-lint@^0.4.12, ember-concurrency@^0.7.8, ember-test-selectors@^0.0.3
Installed packages for tooling via npm.
Install failed. Could not find addon with name: ember-cli-eslint@^1.7.0
```

We have an Ember addon for installing other addons that is currently running into issues. The `afterInstall` hook in the blueprint looks a like this:

```js
  afterInstall: function() {
    var blueprint = this;
    var project = this.project;

    return blueprint.addAddonsToProject({
      packages: [
        { name: 'ember-cli-eslint', target: '^1.7.0' },
        { name: 'ember-cli-template-lint', target: '^0.4.12' },
        { name: 'ember-concurrency', target: '^0.7.8' },
        { name: 'ember-test-selectors', target: '^0.0.3' }
      ]
    }).then(function() {
      return blueprint.addPackagesToProject([
        { name: 'babel-eslint', target: '^6.1.2' },
        { name: 'ember-cli-deploy', target: '^0.6.4' },
      ])
    })
```

I can reproduce the issue with [this pared down addon](https://github.com/binhums/bug-add-addons-to-project).

This looks similar to #5880, but I believe it is distinct.

Verified against master, I have a potential fix incoming.

---

Output from `ember version --verbose && npm --version`:
```
ember-cli: 2.8.0
http_parser: 2.5.2
node: 4.4.5
v8: 4.5.103.35
uv: 1.8.0
zlib: 1.2.8
ares: 1.10.1-DEV
icu: 56.1
modules: 46
openssl: 1.0.2h
os: darwin x64
2.15.5
```


Pull request description:
Use --save-dev by default when installing addons
Currently, installing an addon with `this.addAddonsToProject` in a blueprint will fail. See #6318 for details.

Having stepped through the code it looks like the issue is that by default npm is not run with the `save-dev` flag. The consequence is that when ember-cli looks for the installed addon it can't find it.

This PR makes npm be called with a `--save-dev` argument if no specific options are specified. Namely by making `blueprintOptions` default to `{ saveDev: true}`.

My understanding is that the following happens:

1. `this.addAddonsToProject` is called with a list of addons (say in the `afterInstall` hook).
2. These are passed to the `addon-install` task.
3. This triggers `npm install` with the list of packages without `--save-dev`.
4. The addon is not added to `packages.json`
5. The addons are reloaded
6. Ember-cli tries to run the default blueprints for the newly installed addons.
7. Ember tries to find the addon in `project.addons` but it isn't there.
8. Error message, install failed.

I'm not 100% convinced this is the best place to set a default, we may equivalently want to set this up in the `addon-install` task.

I've added a unit test, we could potentially add an acceptance test here too.

Fixes #6318

Patch:
diff --git a/lib/tasks/addon-install.js b/lib/tasks/addon-install.js
index 4649916b7..ebe94730e 100644
--- a/lib/tasks/addon-install.js
+++ b/lib/tasks/addon-install.js
@@ -38,7 +38,7 @@ module.exports = Task.extend({
     return npmInstall.run({
       'packages': packageNames,
       'save': commandOptions.save,
-      'save-dev': !commandOptions.save && commandOptions.saveDev,
+      'save-dev': !commandOptions.save,
       'save-exact': true
     }).then(function() {
       return this.project.reloadAddons();


Test patch:
diff --git a/tests/unit/tasks/addon-install-test.js b/tests/unit/tasks/addon-install-test.js
new file mode 100644
index 000000000..28a0742b4
--- /dev/null
+++ b/tests/unit/tasks/addon-install-test.js
@@ -0,0 +1,123 @@
+'use strict';
+
+var AddonInstallTask = require('../../../lib/tasks/addon-install');
+var MockProject      = require('../../helpers/mock-project');
+var expect           = require('chai').expect;
+var CoreObject       = require('core-object');
+var Promise          = require('rsvp').Promise;
+
+describe('addon install task', function() {
+  var ui;
+  var project;
+
+  beforeEach(function() {
+    ui = {
+      startProgress: function() {
+
+      }
+    };
+  });
+
+  afterEach(function() {
+    // ui.stopProgress();
+    ui = undefined;
+    project = undefined;
+  });
+
+  describe('when no save flag specified in blueprintOptions', function() {
+    it('calls npm install with --save-dev as a default', function(done) {
+      var MockNpmInstallTask = CoreObject.extend({
+        run: function(options) {
+          expect(options.save).to.not.be.true;
+          expect(options['save-dev']).to.be.true;
+          done();
+          return Promise.resolve();
+        }
+      });
+
+      var addonInstallTask = new AddonInstallTask({
+        ui: ui,
+        project: project,
+        NpmInstallTask: MockNpmInstallTask
+      });
+
+      addonInstallTask.run({});
+    });
+  });
+
+  describe('when save flag specified in blueprintOptions', function() {
+    it('calls npm install with --save', function(done) {
+      var MockNpmInstallTask = CoreObject.extend({
+        run: function(options) {
+          expect(options.save).to.be.true;
+          expect(options['save-dev']).to.not.be.true;
+          done();
+          return Promise.resolve();
+        }
+      });
+
+      var addonInstallTask = new AddonInstallTask({
+        ui: ui,
+        project: project,
+        NpmInstallTask: MockNpmInstallTask
+      });
+
+      addonInstallTask.run({
+        blueprintOptions: {
+          save: true
+        }
+      });
+    });
+  });
+
+  describe('when saveDev flag specified in blueprintOptions', function() {
+    it('calls npm install with --save-dev', function(done) {
+      var MockNpmInstallTask = CoreObject.extend({
+        run: function(options) {
+          expect(options.save).to.not.be.true;
+          expect(options['save-dev']).to.be.true;
+          done();
+          return Promise.resolve();
+        }
+      });
+
+      var addonInstallTask = new AddonInstallTask({
+        ui: ui,
+        project: project,
+        NpmInstallTask: MockNpmInstallTask
+      });
+
+      addonInstallTask.run({
+        blueprintOptions: {
+          saveDev: true
+        }
+      });
+    });
+  });
+
+  describe('when both save and saveDev flag specified in blueprintOptions', function() {
+    it('calls npm install with --save', function(done) {
+      var MockNpmInstallTask = CoreObject.extend({
+        run: function(options) {
+          expect(options.save).to.be.true;
+          expect(options['save-dev']).to.not.be.true;
+          done();
+          return Promise.resolve();
+        }
+      });
+
+      var addonInstallTask = new AddonInstallTask({
+        ui: ui,
+        project: project,
+        NpmInstallTask: MockNpmInstallTask
+      });
+
+      addonInstallTask.run({
+        blueprintOptions: {
+          save: true,
+          saveDev: true
+        }
+      });
+    });
+  });
+});