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

Issue:
Error: Cannot find module 'bower'
https://github.com/ember-cli/ember-try/pull/157 appears to have broken CI for `ember-cli-shims` (see https://travis-ci.org/ember-cli/ember-cli-shims/jobs/300982718) and I assume probably for other addons too.

`ember-cli-shims` is running CI once per day, yesterday it worked and today it is broken with this error:

```
Error: Cannot find module 'bower' from '/home/travis/build/ember-cli/ember-cli-shims/node_modules/ember-cli/bin'
    at /home/travis/build/ember-cli/ember-cli-shims/node_modules/resolve/lib/async.js:48:31
    at processDirs (/home/travis/build/ember-cli/ember-cli-shims/node_modules/resolve/lib/async.js:182:39)
    at ondir (/home/travis/build/ember-cli/ember-cli-shims/node_modules/resolve/lib/async.js:197:13)
    at load (/home/travis/build/ember-cli/ember-cli-shims/node_modules/resolve/lib/async.js:80:43)
    at onex (/home/travis/build/ember-cli/ember-cli-shims/node_modules/resolve/lib/async.js:105:17)
    at /home/travis/build/ember-cli/ember-cli-shims/node_modules/resolve/lib/async.js:26:73
    at FSReqWrap.oncomplete (fs.js:123:15)
```

/cc @kategengler @hjdivad @rwjblue @twokul 

Pull request:
 Ensure `bower` is not required when no bower dependencies are present.
Fixes #161 

Patch:
diff --git a/lib/dependency-manager-adapters/bower.js b/lib/dependency-manager-adapters/bower.js
index 31e909d..2e52e85 100644
--- a/lib/dependency-manager-adapters/bower.js
+++ b/lib/dependency-manager-adapters/bower.js
@@ -7,7 +7,7 @@ var path = require('path');
 var extend = require('extend');
 var debug = require('debug')('ember-try:dependency-manager-adapter:bower');
 var rimraf = RSVP.denodeify(require('rimraf'));
-var resolve = RSVP.denodeify(require('resolve'));
+var resolve = require('resolve');
 var findEmberPath = require('../utils/find-ember-path');
 
 module.exports = CoreObject.extend({
@@ -18,9 +18,11 @@ module.exports = CoreObject.extend({
   },
   bowerJSONFileName: 'bower.json',
   bowerJSONBackupFileName: 'bower.json.ember-try',
-  defaultBowerJSON: {
-    name: 'ember-try-placeholder',
-    dependencies: {}
+  defaultBowerJSON: function() {
+    return {
+      name: 'ember-try-placeholder',
+      dependencies: {},
+    };
   },
   configKey: 'bower',
   setup: function() {
@@ -45,7 +47,7 @@ module.exports = CoreObject.extend({
       var backupBowerFile = path.join(adapter.cwd, adapter.bowerJSONBackupFileName);
       baseBowerJSON = JSON.parse(fs.readFileSync(backupBowerFile));
     } else {
-      baseBowerJSON = this.defaultBowerJSON;
+      baseBowerJSON = this.defaultBowerJSON();
     }
 
     var newBowerJSON = adapter._bowerJSONForDependencySet(baseBowerJSON, depSet);
@@ -54,13 +56,28 @@ module.exports = CoreObject.extend({
 
     fs.writeFileSync(bowerFile, JSON.stringify(newBowerJSON, null, 2));
   },
+
+  /* Compute whether or not there are bower dependencies to install.
+   *
+   * @return true iff `depSet` has either dependencies or devDependencies
+   */
+  _hasDependencies(depSet) {
+    if (!depSet) { return false; }
+
+    var dependencies = Object.assign({}, depSet.dependencies, depSet.devDependencies);
+
+    return Object.keys(dependencies).length > 0;
+  },
+
   changeToDependencySet: function(depSet) {
     var adapter = this;
     depSet = this._getDependencySetAccountingForDeprecatedTopLevelKeys(depSet);
 
     debug('Changing to dependency set: %s', JSON.stringify(depSet));
 
-    if (!depSet) { return RSVP.resolve([]); }
+    if (!this._hasDependencies(depSet)) {
+      return RSVP.resolve([]);
+    }
 
     adapter._writeBowerFileWithDepSetChanges(depSet);
 
@@ -89,9 +106,9 @@ module.exports = CoreObject.extend({
       }).catch(function(e) {
         console.log('Error cleaning up bower scenario:', e);
       })
-      .then(function() {
-        return adapter._install();
-      });
+        .then(function() {
+          return adapter._install();
+        });
     } else {
       return rimraf(path.join(adapter.cwd, adapter.bowerJSONFileName)).then(function() {
         return rimraf(path.join(adapter.cwd, 'bower_components'));
@@ -177,13 +194,12 @@ module.exports = CoreObject.extend({
     var adapter = this;
     return findEmberPath(adapter.cwd).then(function(emberPath) {
       /* Find bower's entry point module relative to ember-cli's entry point script */
-      return adapter._resolveModule('bower', { basedir: path.dirname(emberPath) })
-        .then(function(bowerPath) {
-          return '"' + path.join(bowerPath, '..', '..', 'bin', 'bower') + '"';
-        });
+      var bowerPath = adapter._resolveModule('bower', { basedir: path.dirname(emberPath) });
+
+      return '"' + path.join(bowerPath, '..', '..', 'bin', 'bower') + '"';
     });
   },
   _resolveModule: function(moduleName, options) {
-    return resolve(moduleName, options);
+    return resolve.sync(moduleName, options);
   }
 });
diff --git a/lib/utils/dependency-manager-adapter-factory.js b/lib/utils/dependency-manager-adapter-factory.js
index 6ece4bb..9890a3a 100644
--- a/lib/utils/dependency-manager-adapter-factory.js
+++ b/lib/utils/dependency-manager-adapter-factory.js
@@ -20,10 +20,8 @@ module.exports = {
         hasBower = true;
       }
     });
-    if (hasNpm) {
+    if (hasNpm || hasBower) {
       adapters.push(new NpmAdapter({ cwd: root, managerOptions: config.npmOptions, useYarnCommand: config.useYarn }));
-    }
-    if (hasBower) {
       adapters.push(new BowerAdapter({ cwd: root, managerOptions: config.bowerOptions }));
     }
     return adapters;
diff --git a/package.json b/package.json
index 894aaf5..af56b01 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
     "glob": "^7.0.0",
     "istanbul": "1.0.0-alpha.2",
     "loader.js": "^4.0.1",
-    "mocha": "^2.2.5",
+    "mocha": "^4.0.0",
     "mockery": "^1.4.0",
     "tmp-sync": "^1.1.0"
   },


Test patch:
diff --git a/test/dependency-manager-adapters/bower-adapter-test.js b/test/dependency-manager-adapters/bower-adapter-test.js
index bf6641c..8d9f037 100644
--- a/test/dependency-manager-adapters/bower-adapter-test.js
+++ b/test/dependency-manager-adapters/bower-adapter-test.js
@@ -88,6 +88,47 @@ describe('bowerAdapter', function() {
     });
   });
 
+  describe('#changeToDependencySet', function() {
+    it('if there are no bower dependencies, nothing is done', function() {
+      var stubbedRun = function() {
+        throw new Error('Should not run anything');
+      };
+
+      let adapter = new BowerAdapter({ cwd: tmpdir, run: stubbedRun });
+
+      return adapter.setup()
+        .then(function() {
+          return adapter.changeToDependencySet({ });
+        })
+        .then(function() {
+          return adapter.cleanup();
+        });
+    });
+
+    it('if bower dependencies are the root of the dep set they are detected', function() {
+      let stubbedRunRan = false;
+      var stubbedRun = function(command, args, opts) {
+        expect(command).to.equal('node');
+        expect(args[0]).to.match(/bower/);
+        expect(args[1]).to.equal('install');
+        expect(args[2]).to.equal('--config.interactive=false');
+        expect(opts).to.have.property('cwd', tmpdir);
+        stubbedRunRan = true;
+        return RSVP.resolve();
+      };
+
+      let adapter = new BowerAdapter({ cwd: tmpdir, run: stubbedRun });
+      return adapter.setup()
+        .then(function() {
+          return adapter.changeToDependencySet({ dependencies: { 'ember': '*' } });
+        })
+        .then(function() {
+          expect(stubbedRunRan).to.equal(true);
+          return adapter.cleanup();
+        });
+    });
+  });
+
   describe('#_install', function() {
     it('removes bower_components', function() {
       var stubbedRun = function() {
@@ -174,6 +215,7 @@ describe('bowerAdapter', function() {
       writeJSONFile('bower.json.ember-try', bowerJSON);
 
       new BowerAdapter({ cwd: tmpdir })._writeBowerFileWithDepSetChanges(depSet);
+
       assertFileContainsJSON('bower.json', {
         dependencies: {
           jquery: '2.1.3'
diff --git a/test/tasks/try-each-test.js b/test/tasks/try-each-test.js
index ab61401..6536c1a 100644
--- a/test/tasks/try-each-test.js
+++ b/test/tasks/try-each-test.js
@@ -147,6 +147,8 @@ describe('tryEach', function() {
         _on: function() {}
       });
 
+      writeJSONFile('package.json', fixturePackage);
+      fs.mkdirSync('node_modules');
       writeJSONFile('bower.json', fixtureBower);
       return tryEachTask.run(legacyConfig.scenarios, {}).then(function(exitCode) {
         expect(exitCode).to.equal(0, 'exits 0 when all scenarios succeed');
@@ -191,6 +193,8 @@ describe('tryEach', function() {
         _on: function() {}
       });
 
+      writeJSONFile('package.json', fixturePackage);
+      fs.mkdirSync('node_modules');
       writeJSONFile('bower.json', fixtureBower);
       return tryEachTask.run(legacyConfig.scenarios, {}).then(function(exitCode) {
         expect(exitCode).to.equal(1);
@@ -231,6 +235,9 @@ describe('tryEach', function() {
         _on: function() {}
       });
 
+      writeJSONFile('package.json', fixturePackage);
+      fs.mkdirSync('node_modules');
+
       expect(fs.existsSync('bower.json')).to.eql(false);
       return tryEachTask.run(legacyConfig.scenarios, {}).then(function() {
         expect(output).to.include('All 5 scenarios succeeded');
diff --git a/test/utils/dependency-manager-adapter-factory-test.js b/test/utils/dependency-manager-adapter-factory-test.js
index 320d782..90e242d 100644
--- a/test/utils/dependency-manager-adapter-factory-test.js
+++ b/test/utils/dependency-manager-adapter-factory-test.js
@@ -5,35 +5,39 @@ var DependencyManagerAdapterFactory = require('../../lib/utils/dependency-manage
 
 describe('DependencyManagerAdapterFactory', function() {
   describe('generateFromConfig', function() {
-    it('creates npm adapter when config has npm key', function() {
+    it('creates both adapters, in order, when there is only an npm key', function() {
       var adapters = DependencyManagerAdapterFactory.generateFromConfig({ scenarios: [{ npm: {} }] }, 'here');
       expect(adapters[0].configKey).to.equal('npm');
-      expect(adapters.length).to.equal(1);
+      expect(adapters[1].configKey).to.equal('bower');
+      expect(adapters.length).to.equal(2);
     });
 
-    it('creates bower adapter when config has bower key', function() {
+    it('creates both adapters, in order, when there is only a bower key', function() {
       var adapters = DependencyManagerAdapterFactory.generateFromConfig({ scenarios: [{ bower: {} }] }, 'here');
-      expect(adapters[0].configKey).to.equal('bower');
-      expect(adapters.length).to.equal(1);
+      expect(adapters[0].configKey).to.equal('npm');
+      expect(adapters[1].configKey).to.equal('bower');
+      expect(adapters.length).to.equal(2);
     });
 
-    it('creates both adapters, with npm first, when it has both keys', function() {
+    it('creates both adapters, in order, when both keys are present', function() {
       var adapters = DependencyManagerAdapterFactory.generateFromConfig({ scenarios: [{ bower: {}, npm: {} }] }, 'here');
       expect(adapters[0].configKey).to.equal('npm');
       expect(adapters[1].configKey).to.equal('bower');
       expect(adapters.length).to.equal(2);
     });
 
-    it('creates bower adapter when legacy dependenies key', function() {
+    it('creates both adapters, in order, when there is only a legacy top-level dependencies', function() {
       var adapters = DependencyManagerAdapterFactory.generateFromConfig({ scenarios: [{ dependencies: {} }] }, 'here');
-      expect(adapters[0].configKey).to.equal('bower');
-      expect(adapters.length).to.equal(1);
+      expect(adapters[0].configKey).to.equal('npm');
+      expect(adapters[1].configKey).to.equal('bower');
+      expect(adapters.length).to.equal(2);
     });
 
-    it('creates bower adapter when legacy devDependencies key', function() {
+    it('creates both adapters, in order, when there is only a legacy top-level devDependencies', function() {
       var adapters = DependencyManagerAdapterFactory.generateFromConfig({ scenarios: [{ devDependencies: {} }] }, 'here');
-      expect(adapters[0].configKey).to.equal('bower');
-      expect(adapters.length).to.equal(1);
+      expect(adapters[0].configKey).to.equal('npm');
+      expect(adapters[1].configKey).to.equal('bower');
+      expect(adapters.length).to.equal(2);
     });
   });
 });