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

Issue:
Remove remaining coupling to JSHint
Currently our `app` and `addon` blueprints still include `.jshintrc` files (see https://github.com/ember-cli/ember-cli/tree/master/blueprints/app/files), which are now also provided by the default blueprint in `ember-cli-jshint` (see https://github.com/ember-cli/ember-cli-jshint/pull/21). To remove the duplication we should remove the `.jshintrc` files from our blueprint and run the `ember-cli-jshint` blueprint once the dependency is installed.

Unfortunately it's not quite as simple as adding the `lookupBlueprint().install()` call to the `afterInstall()` hook of the `app` and `addon` blueprints, because that hook will run _before_ `npm install` was executed. So currently possible is only:
1. `app` blueprint adds a `package.json` file including the `ember-cli-jshint` dependency
2. `app` blueprint tries to install `ember-cli-jshint` blueprint but fails because the dependency is not yet installed
3. `npm install` will install the `ember-cli-jshint` dependency but not run the corresponding blueprint

Now I am looking for ways to install the blueprint _after_ `npm install` has finished without breaking the world. Is there a good way to do this in the current code base?

Should we maybe implement a public `installAddon()` method that only resolves once `npm install` for that addon has finished?

/cc @rwjblue @stefanpenner 


Pull request description:
Install JSHint files via "ember-cli-jshint" blueprint
This PR resolves #6269. It changes the steps in the `ember init` flow to be: install blueprint, `npm install`, `bower install`, `ember generate ember-cli-jshint`, `git init && git add && git commit`.

This will bring us closer to switching to `ember-cli-eslint` instead.

Patch:
diff --git a/blueprints/app/files/.jshintrc b/blueprints/app/files/.jshintrc
deleted file mode 100644
index d421faa30..000000000
--- a/blueprints/app/files/.jshintrc
+++ /dev/null
@@ -1,32 +0,0 @@
-{
-  "predef": [
-    "document",
-    "window",
-    "-Promise"
-  ],
-  "browser": true,
-  "boss": true,
-  "curly": true,
-  "debug": false,
-  "devel": true,
-  "eqeqeq": true,
-  "evil": true,
-  "forin": false,
-  "immed": false,
-  "laxbreak": false,
-  "newcap": true,
-  "noarg": true,
-  "noempty": false,
-  "nonew": false,
-  "nomen": false,
-  "onevar": false,
-  "plusplus": false,
-  "regexp": false,
-  "undef": true,
-  "sub": true,
-  "strict": false,
-  "white": false,
-  "eqnull": true,
-  "esversion": 6,
-  "unused": true
-}
diff --git a/blueprints/app/files/package.json b/blueprints/app/files/package.json
index e19394da0..73f6ddb5c 100644
--- a/blueprints/app/files/package.json
+++ b/blueprints/app/files/package.json
@@ -24,7 +24,6 @@
     "ember-cli-htmlbars": "^1.1.1",
     "ember-cli-htmlbars-inline-precompile": "^0.3.3",
     "ember-cli-inject-live-reload": "^1.4.1",
-    "ember-cli-jshint": "^2.0.1",
     "ember-cli-qunit": "^3.0.1",
     "ember-cli-sri": "^2.1.0",
     "ember-cli-test-loader": "^1.1.0",
diff --git a/lib/commands/init.js b/lib/commands/init.js
index 2fe77dfe0..502534307 100644
--- a/lib/commands/init.js
+++ b/lib/commands/init.js
@@ -65,6 +65,14 @@ module.exports = Command.extend({
         analytics: this.analytics,
         project: this.project
       });
+
+      var addonInstall = new this.tasks.AddonInstall({
+        ui:             this.ui,
+        analytics:      this.analytics,
+        project:        this.project,
+        NpmInstallTask: this.tasks.NpmInstall,
+        BlueprintTask:  this.tasks.GenerateFromBlueprint
+      });
     }
 
     if (!commandOptions.skipBower) {
@@ -102,15 +110,12 @@ module.exports = Command.extend({
     return installBlueprint.run(blueprintOpts)
       .then(function() {
         logger.info('after:installblueprint');
-        if (commandOptions.skipGit === false) {
-          return gitInit.run(commandOptions, rawArgs);
-        }
-      })
-      .then(function() {
         if (!commandOptions.skipNpm) {
           return npmInstall.run({
             verbose: commandOptions.verbose,
             optional: false
+          }).then(function() {
+            project.setupNodeModulesPath();
           });
         }
       })
@@ -120,6 +125,20 @@ module.exports = Command.extend({
             verbose: commandOptions.verbose
           });
         }
+      })
+      .then(function() {
+        // skip `ember generate ember-cli-jshint` if the plugin hasn't been installed
+        if (!commandOptions.skipNpm) {
+          return addonInstall.run({
+            verbose: commandOptions.verbose,
+            packages: ['ember-cli-jshint']
+          });
+        }
+      })
+      .then(function() {
+        if (commandOptions.skipGit === false) {
+          return gitInit.run(commandOptions, rawArgs);
+        }
       });
   }
 });


Test patch:
diff --git a/blueprints/app/files/tests/.jshintrc b/blueprints/app/files/tests/.jshintrc
deleted file mode 100644
index d2bd11343..000000000
--- a/blueprints/app/files/tests/.jshintrc
+++ /dev/null
@@ -1,52 +0,0 @@
-{
-  "predef": [
-    "document",
-    "window",
-    "location",
-    "setTimeout",
-    "$",
-    "-Promise",
-    "define",
-    "console",
-    "visit",
-    "exists",
-    "fillIn",
-    "click",
-    "keyEvent",
-    "triggerEvent",
-    "find",
-    "findWithAssert",
-    "wait",
-    "DS",
-    "andThen",
-    "currentURL",
-    "currentPath",
-    "currentRouteName"
-  ],
-  "node": false,
-  "browser": false,
-  "boss": true,
-  "curly": true,
-  "debug": false,
-  "devel": false,
-  "eqeqeq": true,
-  "evil": true,
-  "forin": false,
-  "immed": false,
-  "laxbreak": false,
-  "newcap": true,
-  "noarg": true,
-  "noempty": false,
-  "nonew": false,
-  "nomen": false,
-  "onevar": false,
-  "plusplus": false,
-  "regexp": false,
-  "undef": true,
-  "sub": true,
-  "strict": false,
-  "white": false,
-  "eqnull": true,
-  "esversion": 6,
-  "unused": true
-}
diff --git a/tests/acceptance/addon-dummy-generate-test.js b/tests/acceptance/addon-dummy-generate-test.js
index e34d52cee..3ed4504cc 100644
--- a/tests/acceptance/addon-dummy-generate-test.js
+++ b/tests/acceptance/addon-dummy-generate-test.js
@@ -43,7 +43,13 @@ describe('Acceptance: ember generate in-addon-dummy', function() {
       'my-addon',
       '--skip-npm',
       '--skip-bower'
-    ]);
+    ]).then(addJSHint);
+  }
+
+  function addJSHint() {
+    var pkg = fs.readJsonSync('package.json');
+    pkg.devDependencies['ember-cli-jshint'] = '*';
+    fs.writeJsonSync('package.json', pkg);
   }
 
   function generateInAddon(args) {
@@ -257,7 +263,6 @@ describe('Acceptance: ember generate in-addon-dummy', function() {
   it('dummy server', function() {
     return generateInAddon(['server', '--dummy']).then(function() {
       expect(file('server/index.js')).to.exist;
-      expect(file('server/.jshintrc')).to.exist;
     });
   });
 
diff --git a/tests/acceptance/addon-generate-test.js b/tests/acceptance/addon-generate-test.js
index 0658129d6..5f8c7ddd8 100644
--- a/tests/acceptance/addon-generate-test.js
+++ b/tests/acceptance/addon-generate-test.js
@@ -43,7 +43,13 @@ describe('Acceptance: ember generate in-addon', function() {
       name,
       '--skip-npm',
       '--skip-bower'
-    ]);
+    ]).then(addJSHint);
+  }
+
+  function addJSHint() {
+    var pkg = fs.readJsonSync('package.json');
+    pkg.devDependencies['ember-cli-jshint'] = '*';
+    fs.writeJsonSync('package.json', pkg);
   }
 
   function generateInAddon(args) {
@@ -286,7 +292,6 @@ describe('Acceptance: ember generate in-addon', function() {
   it('in-addon server', function() {
     return generateInAddon(['server']).then(function() {
       expect(file('server/index.js')).to.exist;
-      expect(file('server/.jshintrc')).to.exist;
     });
   });
 
diff --git a/tests/acceptance/destroy-test.js b/tests/acceptance/destroy-test.js
index 8859c80d2..6bab9c81e 100644
--- a/tests/acceptance/destroy-test.js
+++ b/tests/acceptance/destroy-test.js
@@ -305,7 +305,6 @@ describe('Acceptance: ember destroy', function() {
       .then(function() { return destroy(['http-mock', 'foo']); })
       .then(function() {
         expect(file('server/index.js')).to.exist;
-        expect(file('server/.jshintrc')).to.exist;
       });
   });
 
diff --git a/tests/acceptance/generate-test.js b/tests/acceptance/generate-test.js
index 373b052b9..1bceb5898 100755
--- a/tests/acceptance/generate-test.js
+++ b/tests/acceptance/generate-test.js
@@ -16,6 +16,7 @@ var mkTmpDirIn       = require('../../lib/utilities/mk-tmp-dir-in');
 var chai = require('../chai');
 var expect = chai.expect;
 var file = chai.file;
+var dir = chai.dir;
 
 describe('Acceptance: ember generate', function() {
   this.timeout(20000);
@@ -48,7 +49,13 @@ describe('Acceptance: ember generate', function() {
       '--name=my-app',
       '--skip-npm',
       '--skip-bower'
-    ]);
+    ]).then(addJSHint);
+  }
+
+  function addJSHint() {
+    var pkg = fs.readJsonSync('package.json');
+    pkg.devDependencies['ember-cli-jshint'] = '*';
+    fs.writeJsonSync('package.json', pkg);
   }
 
   function generate(args) {
@@ -396,7 +403,6 @@ describe('Acceptance: ember generate', function() {
   it('server', function() {
     return generate(['server']).then(function() {
       expect(file('server/index.js')).to.exist;
-      expect(file('server/.jshintrc')).to.exist;
     });
   });
 
@@ -408,7 +414,7 @@ describe('Acceptance: ember generate', function() {
 
   it('lib', function() {
     return generate(['lib']).then(function() {
-      expect(file('lib/.jshintrc')).to.exist;
+      expect(dir('lib')).to.exist;
     });
   });
 
diff --git a/tests/acceptance/pods-generate-test.js b/tests/acceptance/pods-generate-test.js
index cea1f6893..de10a4ee1 100644
--- a/tests/acceptance/pods-generate-test.js
+++ b/tests/acceptance/pods-generate-test.js
@@ -49,7 +49,7 @@ describe('Acceptance: ember generate pod', function() {
       '--name=my-app',
       '--skip-npm',
       '--skip-bower'
-    ]);
+    ]).then(addJSHint);
   }
 
   function initAddon() {
@@ -58,7 +58,13 @@ describe('Acceptance: ember generate pod', function() {
       'my-addon',
       '--skip-npm',
       '--skip-bower'
-    ]);
+    ]).then(addJSHint);
+  }
+
+  function addJSHint() {
+    var pkg = fs.readJsonSync('package.json');
+    pkg.devDependencies['ember-cli-jshint'] = '*';
+    fs.writeJsonSync('package.json', pkg);
   }
 
   function initInRepoAddon() {
diff --git a/tests/acceptance/smoke-test-slow.js b/tests/acceptance/smoke-test-slow.js
index 4d10d5ec8..17f0641f3 100644
--- a/tests/acceptance/smoke-test-slow.js
+++ b/tests/acceptance/smoke-test-slow.js
@@ -171,9 +171,8 @@ describe('Acceptance: smoke-test', function() {
 
         output = output.join(EOL);
 
-        expect(output).to.match(/JSHint/, 'JSHint should be run');
         expect(output).to.match(/fail\s+0/, 'no failures');
-        expect(output).to.match(/pass\s+12/, '1 passing');
+        expect(output).to.match(/pass\s+2/, '2 passing');
       });
   });
 
diff --git a/tests/unit/blueprints/lib-test.js b/tests/unit/blueprints/lib-test.js
index 85bb1340b..bb675c521 100644
--- a/tests/unit/blueprints/lib-test.js
+++ b/tests/unit/blueprints/lib-test.js
@@ -22,24 +22,24 @@ describe('Acceptance: ember generate and destroy lib', function() {
       .then(function() {
         return emberGenerateDestroy(args, function(file) {
           expect(dir('lib')).to.exist;
-          expect(file('lib/.jshintrc')).to.exist;
+          expect(file('lib/.jshintrc')).to.not.exist;
         });
       });
   });
 
-  it('lib foo without ember-cli-jshint', function() {
+  it('lib foo with ember-cli-jshint', function() {
     var args = ['lib', 'foo'];
 
     return emberNew()
       .then(function() {
         return modifyPackages([
-          {name: 'ember-cli-jshint', delete: true},
+          {name: 'ember-cli-jshint', dev: true},
         ]);
       })
       .then(function() {
         return emberGenerateDestroy(args, function(file) {
           expect(dir('lib')).to.exist;
-          expect(file('lib/.jshintrc')).to.not.exist;
+          expect(file('lib/.jshintrc')).to.exist;
         });
       });
   });
diff --git a/tests/unit/blueprints/server-test.js b/tests/unit/blueprints/server-test.js
index 915bdabff..16dd72fa9 100644
--- a/tests/unit/blueprints/server-test.js
+++ b/tests/unit/blueprints/server-test.js
@@ -26,26 +26,26 @@ describe('Acceptance: ember generate and destroy server', function() {
       })
       .then(function() {
         expect(file('server/index.js')).to.contain('module.exports = function(app) {');
-        expect(file('server/.jshintrc')).to.exist;
+        expect(file('server/.jshintrc')).to.not.exist;
 
         // TODO: assert that `morgan` and `glob` dependencies were installed
       });
   });
 
-  it('server without ember-cli-jshint', function() {
+  it('server with ember-cli-jshint', function() {
     var args = ['server'];
 
     return emberNew()
       .then(function() {
         return modifyPackages([
-          {name: 'ember-cli-jshint', delete: true},
+          {name: 'ember-cli-jshint', dev: true},
         ]);
       })
       .then(function() {
         return emberGenerate(args);
       })
       .then(function() {
-        expect(file('server/.jshintrc')).to.not.exist;
+        expect(file('server/.jshintrc')).to.exist;
       });
   });
 });
diff --git a/tests/unit/commands/init-test.js b/tests/unit/commands/init-test.js
index 42226d89d..4511d874d 100644
--- a/tests/unit/commands/init-test.js
+++ b/tests/unit/commands/init-test.js
@@ -21,6 +21,7 @@ describe('init command', function() {
     ui = new MockUI();
     analytics = new MockAnalytics();
     tasks = {
+      AddonInstall: Task.extend({}),
       InstallBlueprint: Task.extend({}),
       NpmInstall: Task.extend({}),
       BowerInstall: Task.extend({})