Repository: ember-cli/ember-cli
Pull request #7947 (2018-07-31 21:04:08)

Adding support for in repo addons in non-conventional directories
Currently, ember-cli assumes all in-repo addons should live in `lib`. As such, blueprints that are run to generate entities in in-repo addons also assume `lib`. This PR allows for custom paths to be provided while defaulting to `lib`. 


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..c7fad43eb 100755
--- a/blueprints/in-repo-addon/index.js
+++ b/blueprints/in-repo-addon/index.js
@@ -3,20 +3,43 @@
 const fs = require('fs-extra');
 const path = require('path');
 const stringUtil = require('ember-cli-string-utils');
-const Blueprint = require('../../lib/models/blueprint');
 const stringifyAndNormalize = require('../../lib/utilities/stringify-and-normalize');
 
 module.exports = {
   description: 'The blueprint for addon in repo ember-cli addons.',
 
   beforeInstall(options) {
-    let libBlueprint = Blueprint.lookup('lib', {
-      ui: this.ui,
-      analytics: this.analytics,
-      project: this.project,
-    });
+    let { root } = this._processTokens(options.entity.name);
 
-    return libBlueprint.install(options);
+    fs.mkdirsSync(root);
+  },
+
+  _processTokens(name) {
+    let root = 'lib';
+
+    if (name.match(/[./]/)) {
+      root = path.dirname(name);
+      name = path.basename(name);
+    }
+
+    name = stringUtil.dasherize(name);
+
+    return { root, name };
+  },
+
+  locals(options) {
+    let { name } = this._processTokens(options.entity.name);
+
+    return {
+      dasherizedModuleName: name,
+    };
+  },
+
+  fileMapTokens() {
+    return {
+      __root__: options => this._processTokens(options.dasherizedModuleName).root,
+      __name__: options => this._processTokens(options.dasherizedModuleName).name,
+    };
   },
 
   afterInstall(options) {
@@ -30,8 +53,8 @@ module.exports = {
   _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 { root, name } = this._processTokens(options.entity.name);
+    let newPath = [root, name].join('/');
     let paths;
 
     contents['ember-addon'] = contents['ember-addon'] || {};
diff --git a/lib/models/blueprint.js b/lib/models/blueprint.js
index de50d4b91..d527701b5 100644
--- a/lib/models/blueprint.js
+++ b/lib/models/blueprint.js
@@ -389,7 +389,8 @@ let Blueprint = CoreObject.extend({
   */
   _checkInRepoAddonExists(inRepoAddon) {
     if (inRepoAddon) {
-      if (!inRepoAddonExists(inRepoAddon, this.project.root)) {
+      let addon = findAddonByName(this.project, inRepoAddon);
+      if (!addon) {
         throw new SilentError(`You specified the in-repo-addon flag, but the ` +
           `in-repo-addon '${inRepoAddon}' does not exist. Please check the name and try again.`);
       }
@@ -573,6 +574,7 @@ let Blueprint = CoreObject.extend({
     @return {Object}
   */
   _fileMapTokens(options) {
+    let { project } = this;
     let standardTokens = {
       __name__(options) {
         if (options.pod && options.hasPathToken) {
@@ -593,7 +595,9 @@ let Blueprint = CoreObject.extend({
       },
       __root__(options) {
         if (options.inRepoAddon) {
-          return path.join('lib', options.inRepoAddon, 'addon');
+          let addon = findAddonByName(project, options.inRepoAddon);
+          let relativeAddonPath = path.relative(project.root, addon.root);
+          return path.join(relativeAddonPath, 'addon');
         }
         if (options.inDummy) {
           return path.join('tests', 'dummy', 'app');
@@ -1482,9 +1486,14 @@ function hasPathToken(files) {
   return (/__path__/).test(files.join());
 }
 
-function inRepoAddonExists(name, root) {
-  let addonPath = path.join(root, 'lib', name);
-  return Blueprint._existsSync(addonPath);
+function findAddonByName(addonOrProject, name) {
+  let addon = addonOrProject.addons.find(addon => addon.name === name);
+
+  if (addon) {
+    return addon;
+  }
+
+  return addonOrProject.addons.find(addon => findAddonByName(addon, name));
 }
 
 /**


Test patch:
diff --git a/tests/acceptance/in-repo-addon-generate-test.js b/tests/acceptance/in-repo-addon-generate-test.js
index 9fcd2089d..2f23eef17 100644
--- a/tests/acceptance/in-repo-addon-generate-test.js
+++ b/tests/acceptance/in-repo-addon-generate-test.js
@@ -4,6 +4,7 @@ const RSVP = require('rsvp');
 const ember = require('../helpers/ember');
 const fs = require('fs-extra');
 const path = require('path');
+let outputFile = RSVP.denodeify(fs.outputFile);
 let remove = RSVP.denodeify(fs.remove);
 let root = process.cwd();
 let tmproot = path.join(root, 'tmp');
@@ -56,10 +57,32 @@ describe('Acceptance: ember generate in-repo-addon', function() {
     });
   }
 
+  it('in-repo-addon blueprint foo inside alternate path', function() {
+    // build an app with an in-repo addon in a non-standard path
+    return initApp()
+      .then(() =>
+        ember([
+          'generate',
+          'in-repo-addon',
+          './non-lib/other-thing',
+        ])
+      )
+      // generate in project blueprint to allow easier testing of in-repo generation
+      .then(() => outputFile(
+        'blueprints/foo/files/__root__/foos/__name__.js',
+        '/* whoah, empty foo! */'
+      ))
+      // confirm that we can generate into the non-lib path
+      .then(() =>
+        ember(['generate', 'foo', 'bar', '--in-repo-addon=other-thing']).then(function() {
+          expect(file('non-lib/other-thing/addon/foos/bar.js')).to.exist;
+        })
+      );
+  });
+
   it('in-repo-addon adds path to lib', function() {
     return initInRepoAddon().then(function() {
       expect(file('package.json')).to.contain('lib/my-addon');
     });
   });
-
 });