Repository: ember-cli/ember-cli
Pull request #7783 (2018-04-21 21:53:14)

Update `in-repo-addon` blueprint to support module unification
Part of https://github.com/ember-cli/ember-cli/issues/7530

This PR updates the `in-repo-addon` blueprint to generate an in-repo addon in the `packages` directory when the app is MU. It also introduces a new `packages` blueprint which will be the successor to the classic `lib` blueprint.

TODO:

 * [x] wrap MU tests with MU experiment check
 * [x] fix remaining test failures


Patch:
diff --git a/blueprints/in-repo-addon/files/lib/__name__/index.js b/blueprints/in-repo-addon/files/__root__/__name__/index.js
similarity index 100%
rename from blueprints/in-repo-addon/files/lib/__name__/index.js
rename to blueprints/in-repo-addon/files/__root__/__name__/index.js
diff --git a/blueprints/in-repo-addon/files/lib/__name__/package.json b/blueprints/in-repo-addon/files/__root__/__name__/package.json
similarity index 100%
rename from blueprints/in-repo-addon/files/lib/__name__/package.json
rename to blueprints/in-repo-addon/files/__root__/__name__/package.json
diff --git a/blueprints/in-repo-addon/index.js b/blueprints/in-repo-addon/index.js
index ac548cbf1..ce9cabcfc 100755
--- a/blueprints/in-repo-addon/index.js
+++ b/blueprints/in-repo-addon/index.js
@@ -10,13 +10,14 @@ module.exports = {
   description: 'The blueprint for addon in repo ember-cli addons.',
 
   beforeInstall(options) {
-    let libBlueprint = Blueprint.lookup('lib', {
+    let blueprintName = this.fileMapTokens().__root__();
+    let libOrPackagesBlueprint = Blueprint.lookup(blueprintName, {
       ui: this.ui,
       analytics: this.analytics,
       project: this.project,
     });
 
-    return libBlueprint.install(options);
+    return libOrPackagesBlueprint.install(options);
   },
 
   afterInstall(options) {
@@ -27,11 +28,21 @@ module.exports = {
     this._generatePackageJson(options, false);
   },
 
+  fileMapTokens() {
+    let isModuleUnification = this.project.isModuleUnification && this.project.isModuleUnification();
+
+    return {
+      __root__() {
+        return isModuleUnification ? 'packages' : 'lib';
+      },
+    };
+  },
+
   _generatePackageJson(options, isInstall) {
     let packagePath = path.join(this.project.root, 'package.json');
     let contents = this._readJsonSync(packagePath);
     let name = stringUtil.dasherize(options.entity.name);
-    let newPath = ['lib', name].join('/');
+    let newPath = [this.fileMapTokens().__root__(), name].join('/');
     let paths;
 
     contents['ember-addon'] = contents['ember-addon'] || {};
diff --git a/blueprints/packages/files/packages/.eslintrc.js b/blueprints/packages/files/packages/.eslintrc.js
new file mode 100644
index 000000000..104718c32
--- /dev/null
+++ b/blueprints/packages/files/packages/.eslintrc.js
@@ -0,0 +1,6 @@
+module.exports = {
+  env: {
+    node: true,
+    browser: false
+  }
+};
diff --git a/blueprints/packages/files/packages/.jshintrc b/blueprints/packages/files/packages/.jshintrc
new file mode 100644
index 000000000..839c191fa
--- /dev/null
+++ b/blueprints/packages/files/packages/.jshintrc
@@ -0,0 +1,4 @@
+{
+  "node": true,
+  "browser": false
+}
diff --git a/blueprints/packages/index.js b/blueprints/packages/index.js
new file mode 100644
index 000000000..93fb3ef56
--- /dev/null
+++ b/blueprints/packages/index.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const fs = require('fs-extra');
+
+module.exports = {
+  description: 'Generates a packages directory for module unification in-repo addons.',
+
+  normalizeEntityName(name) { return name; },
+
+  beforeInstall() {
+    // make sure to create `packages` directory even if .jshintrc is not created
+    fs.mkdirsSync('packages');
+  },
+
+  files() {
+    return [this.hasJSHint() ? 'packages/.jshintrc' : 'packages/.eslintrc.js'];
+  },
+
+  hasJSHint() {
+    if (this.project) {
+      return 'ember-cli-jshint' in this.project.dependencies();
+    }
+  },
+};


Test patch:
diff --git a/tests/fixtures/help/generate-with-addon.txt b/tests/fixtures/help/generate-with-addon.txt
index fc29b929a..20ab120b3 100644
--- a/tests/fixtures/help/generate-with-addon.txt
+++ b/tests/fixtures/help/generate-with-addon.txt
@@ -48,6 +48,8 @@ ember generate \u001b[33m<blueprint>\u001b[39m \u001b[36m<options...>\u001b[39m
         \u001b[90mGenerates an Ember addon with a module unification layout.\u001b[39m
       module-unification-app \u001b[33m<name>\u001b[39m
         \u001b[90mGenerates an Ember application with a module unification layout.\u001b[39m
+      packages \u001b[33m<name>\u001b[39m
+        \u001b[90mGenerates a packages directory for module unification in-repo addons.\u001b[39m
       server \u001b[33m<name>\u001b[39m
         \u001b[90mGenerates a server directory for mocks and proxies.\u001b[39m
       vendor-shim \u001b[33m<name>\u001b[39m
diff --git a/tests/fixtures/help/generate.txt b/tests/fixtures/help/generate.txt
index 0656d2dd0..70600d79f 100644
--- a/tests/fixtures/help/generate.txt
+++ b/tests/fixtures/help/generate.txt
@@ -21,6 +21,8 @@
         \u001b[90mGenerates an Ember addon with a module unification layout.\u001b[39m
       module-unification-app \u001b[33m<name>\u001b[39m
         \u001b[90mGenerates an Ember application with a module unification layout.\u001b[39m
+      packages \u001b[33m<name>\u001b[39m
+        \u001b[90mGenerates a packages directory for module unification in-repo addons.\u001b[39m
       server \u001b[33m<name>\u001b[39m
         \u001b[90mGenerates a server directory for mocks and proxies.\u001b[39m
       vendor-shim \u001b[33m<name>\u001b[39m
diff --git a/tests/fixtures/help/help.js b/tests/fixtures/help/help.js
index ec06610de..95fe7357a 100644
--- a/tests/fixtures/help/help.js
+++ b/tests/fixtures/help/help.js
@@ -317,6 +317,13 @@ module.exports = {
               anonymousOptions: ['name'],
               overridden: false
             },
+            {
+              name: 'packages',
+              description: 'Generates a packages directory for module unification in-repo addons.',
+              availableOptions: [],
+              anonymousOptions: ['name'],
+              overridden: false
+            },
             {
               name: 'server',
               description: 'Generates a server directory for mocks and proxies.',
diff --git a/tests/fixtures/help/with-addon-blueprints.js b/tests/fixtures/help/with-addon-blueprints.js
index 23fdaeb43..6eab657a1 100644
--- a/tests/fixtures/help/with-addon-blueprints.js
+++ b/tests/fixtures/help/with-addon-blueprints.js
@@ -349,6 +349,13 @@ module.exports = {
               anonymousOptions: ['name'],
               overridden: false
             },
+            {
+              name: 'packages',
+              description: 'Generates a packages directory for module unification in-repo addons.',
+              availableOptions: [],
+              anonymousOptions: ['name'],
+              overridden: false
+            },
             {
               name: 'server',
               description: 'Generates a server directory for mocks and proxies.',
diff --git a/tests/fixtures/help/with-addon-commands.js b/tests/fixtures/help/with-addon-commands.js
index 385708d55..3b9360457 100644
--- a/tests/fixtures/help/with-addon-commands.js
+++ b/tests/fixtures/help/with-addon-commands.js
@@ -317,6 +317,13 @@ module.exports = {
               anonymousOptions: ['name'],
               overridden: false
             },
+            {
+              name: 'packages',
+              description: 'Generates a packages directory for module unification in-repo addons.',
+              availableOptions: [],
+              anonymousOptions: ['name'],
+              overridden: false
+            },
             {
               name: 'server',
               description: 'Generates a server directory for mocks and proxies.',
diff --git a/tests/unit/blueprints/in-repo-addon-test.js b/tests/unit/blueprints/in-repo-addon-test.js
index f20b83f89..33347e6a6 100644
--- a/tests/unit/blueprints/in-repo-addon-test.js
+++ b/tests/unit/blueprints/in-repo-addon-test.js
@@ -11,49 +11,98 @@ const td = require('testdouble');
 
 const expect = require('ember-cli-blueprint-test-helpers/chai').expect;
 const file = require('ember-cli-blueprint-test-helpers/chai').file;
+const experiments = require('../../../lib/experiments');
 
 describe('Acceptance: ember generate and destroy in-repo-addon', function() {
   setupTestHooks(this, {
     cliPath: path.resolve(`${__dirname}/../../..`),
   });
 
-  it('in-repo-addon fooBar', function() {
-    let args = ['in-repo-addon', 'fooBar'];
-
-    return emberNew()
-      .then(function() {
-        expect(fs.readJsonSync('package.json')['ember-addon']).to.be.undefined;
-      })
-      .then(function() {
-        return emberGenerate(args);
-      })
-      .then(function() {
-        expect(file('lib/foo-bar/package.json')).to.exist;
-        expect(file('lib/foo-bar/index.js')).to.exist;
-
-        expect(fs.readJsonSync('lib/foo-bar/package.json')).to.deep.equal({
-          "name": "foo-bar",
-          "keywords": [
-            "ember-addon",
-          ],
+  describe('classic app', function() {
+    it('in-repo-addon fooBar', function() {
+      let args = ['in-repo-addon', 'fooBar'];
+
+      return emberNew()
+        .then(function() {
+          expect(fs.readJsonSync('package.json')['ember-addon']).to.be.undefined;
+        })
+        .then(function() {
+          return emberGenerate(args);
+        })
+        .then(function() {
+          expect(file('lib/foo-bar/package.json')).to.exist;
+          expect(file('lib/foo-bar/index.js')).to.exist;
+
+          expect(fs.readJsonSync('lib/foo-bar/package.json')).to.deep.equal({
+            "name": "foo-bar",
+            "keywords": [
+              "ember-addon",
+            ],
+          });
+
+          expect(fs.readJsonSync('package.json')['ember-addon']).to.deep.equal({
+            "paths": [
+              "lib/foo-bar",
+            ],
+          });
+        })
+        .then(function() {
+          return emberDestroy(args);
+        })
+        .then(function() {
+          expect(file('lib/foo-bar/package.json')).to.not.exist;
+          expect(file('lib/foo-bar/index.js')).to.not.exist;
+
+          expect(fs.readJsonSync('package.json')['ember-addon']['paths']).to.be.undefined;
         });
+    });
+  });
 
-        expect(fs.readJsonSync('package.json')['ember-addon']).to.deep.equal({
-          "paths": [
-            "lib/foo-bar",
-          ],
-        });
-      })
-      .then(function() {
-        return emberDestroy(args);
-      })
-      .then(function() {
-        expect(file('lib/foo-bar/package.json')).to.not.exist;
-        expect(file('lib/foo-bar/index.js')).to.not.exist;
-
-        expect(fs.readJsonSync('package.json')['ember-addon']['paths']).to.be.undefined;
+  if (experiments.MODULE_UNIFICATION) {
+    describe('module unification app', function() {
+      it('in-repo-addon fooBar', function() {
+        let args = ['in-repo-addon', 'fooBar'];
+
+        return emberNew()
+          .then(function() {
+            fs.ensureDirSync('src');
+          })
+          .then(function() {
+            expect(fs.readJsonSync('package.json')['ember-addon']).to.be.undefined;
+          })
+          .then(function() {
+            return emberGenerate(args);
+          })
+          .then(function() {
+            expect(file('packages/foo-bar/package.json')).to.exist;
+            expect(file('packages/foo-bar/index.js')).to.exist;
+
+            expect(fs.readJsonSync('packages/foo-bar/package.json')).to.deep.equal({
+              "name": "foo-bar",
+              "keywords": [
+                "ember-addon",
+              ],
+            });
+
+            expect(fs.readJsonSync('package.json')['ember-addon']).to.deep.equal({
+              "paths": [
+                "packages/foo-bar",
+              ],
+            });
+          })
+          .then(function() {
+            return emberDestroy(args);
+          })
+          .then(function() {
+            expect(file('packages/foo-bar/package.json')).to.not.exist;
+            expect(file('packages/foo-bar/index.js')).to.not.exist;
+
+            expect(fs.readJsonSync('package.json')['ember-addon']['paths']).to.be.undefined;
+          });
       });
-  });
+    });
+  }
+
 });
 
 describe('Unit: in-repo-addon blueprint', function() {
diff --git a/tests/unit/blueprints/packages-test.js b/tests/unit/blueprints/packages-test.js
new file mode 100644
index 000000000..b984d0e32
--- /dev/null
+++ b/tests/unit/blueprints/packages-test.js
@@ -0,0 +1,40 @@
+'use strict';
+
+const path = require('path');
+const blueprintHelpers = require('ember-cli-blueprint-test-helpers/helpers');
+let setupTestHooks = blueprintHelpers.setupTestHooks;
+let emberNew = blueprintHelpers.emberNew;
+let emberGenerateDestroy = blueprintHelpers.emberGenerateDestroy;
+let modifyPackages = blueprintHelpers.modifyPackages;
+
+const expect = require('ember-cli-blueprint-test-helpers/chai').expect;
+const dir = require('chai-files').dir;
+
+describe('Acceptance: ember generate and destroy packages', function() {
+  setupTestHooks(this, {
+    cliPath: path.resolve(`${__dirname}/../../..`),
+  });
+
+  it('packages foo', function() {
+    let args = ['packages', 'foo'];
+
+    return emberNew()
+      .then(() => emberGenerateDestroy(args, file => {
+        expect(dir('packages')).to.exist;
+        expect(file('packages/.jshintrc')).to.not.exist;
+      }));
+  });
+
+  it('packages foo with ember-cli-jshint', function() {
+    let args = ['packages', 'foo'];
+
+    return emberNew()
+      .then(() => modifyPackages([
+        { name: 'ember-cli-jshint', dev: true },
+      ]))
+      .then(() => emberGenerateDestroy(args, file => {
+        expect(dir('packages')).to.exist;
+        expect(file('packages/.jshintrc')).to.exist;
+      }));
+  });
+});