Repository: ember-cli/ember-cli
Pull request #8000 (2018-08-28 21:59:59)

Adding `--in` option for customizing path for in repo addon blueprints
Ember generate allows you to generate entities inside in-repo addons using the `--in-repo-addon` option (not to be confused with the `ember generate` `in-repo-addon` subcommand, which creates an in-repo addon). This option only partially accounts for custom directories (following [this PR](https://github.com/ember-cli/ember-cli/pull/7947)), which is a requirement in some larger applications. 

This PR proposes removing `--in-repo-addon` in favor of a new option: `--in`. The `--in` option differs from the existing `--in-repo-addon` option as it takes a path pointing to the root of the **addon** you would like your blueprint run against.

Valid use cases for the `--in` option would be:


### Case 1

Projects contained in a subdirectory of the root of the repository (monorepos):

```
ember generate component foo-bar --in ./subproject/baz
```

**Result**: 
Will create component files in:

```
// component blueprint 
subproject/baz/app/components/foo-bar.js
subproject/baz/app/templates/components/foo-bar.hbs
    
// component-test blueprint
subproject/tests/integration/components/foo-bar-test.js

// component-addon blueprint
subproject/baz/app/components/foo-bar.js
```

### Case 2

Standard in repo addons (contained in lib):

```
ember generate component foo-bar --in ./lib/baz
```

**Result**: 
Will create component files in:

```
// component blueprint
lib/baz/addon/components/foo-bar.js
lib/baz/addon/templates/components/foo-bar.hbs
    
// component-test blueprint
tests/integration/components/foo-bar-test.js

// component-addon blueprint
lib/baz/app/components/foo-bar.js
```

### Case 3

In repo addon in custom root level path:

```
ember generate component foo-bar --in ./custom/baz
```

**Result**: 
Will create component files in:

```
// component blueprint
custom/baz/addon/components/foo-bar.js
custom/baz/addon/templates/components/foo-bar.hbs
    
// component-test blueprint
tests/integration/components/foo-bar-test.js

// component-addon blueprint
custom/baz/app/components/foo-bar.js
```

### Case 4

In repo addon in custom nested path:

```
ember generate component foo-bar --in ./custom/sub/baz
```

**Result**: 
Will create component files in:

```
// component blueprint
custom/sub/baz/addon/components/foo-bar.js
custom/sub/baz/addon/templates/components/foo-bar.hbs
    
// component-test blueprint
tests/integration/components/foo-bar-test.js

// component-addon blueprint
custom/sub/baz/app/components/foo-bar.js
```

trabus: I would like to request not removing the `--in-repo-addon` option and instead rewire it to use the `--in` option with default arguments pathing to the lib folder, fulfilling the current functionality. Many people I know use the `-ir` or `--in-repo-addon` flag as is currently, and I think removing the option could be a jarring experience.
trabus: Your scenarios described in 2, 3, and 4 should also have generated an app re-export file too.
```
// component addon blueprint
lib/baz/app/components/foo-bar.js
```
I'm fairly certain this is required for components at minimum, since the template for components is included as the layout and not exported from addons like regular templates (please correct me if I've missed something). Any blueprint with an `-addon` tacked onto it will generate this re-export file to be picked up by the consuming application.
rwjblue: @trabus - Thanks for reviewing! FWIW, this doesn't remove `--in-repo-addon` / `--ir` (it just adds `--in`), I totally agree that (at a later time) we should make `--in-repo-addon` leverage `--in` internally...
scalvert: @trabus thanks for the review. I also thought of adding defaulting to `lib`. I think ultimately we could do that. 

As for the reexports, I may have omitted them but ultimately I'm not changing the blueprints at all. This PR merely changes how those blueprints are run. I'm happy to add the reexports for clarity.
jakebixbyavalara: @rwjblue I was mainly responding to his mention up above where he'd discussed the plan to deprecate the `--in-repo-addon` flag in future PRs. I figured rewiring would probably be easier than deprecating anyhow.
> Deprecating the old --in-repo-addon option should be done via the following:
> * add a deprecation warning when using the --in-repo-addon option
> * deprecate at next LTS version of ember-cli

@scalvert I actually think what you've done here is a great primitive and shouldn't be limited to in repo addons, especially with the rise of monorepos and different project file structures. The `--in` flag makes a ton of sense in that regard (as "generate this blueprint *_in_* _blank_"), and reusing it for the internals of other commands actually is a pattern quite I'm fond of.

Regarding the reexports, I just wanted to make sure we were accounting for them. I wasn't sure if you'd left them off or if you were including actual output from your existing PR code. I just wanted to make sure it wasn't overlooked. 👍 
scalvert: I'm with you 100%, @jakebixbyavalara.
scalvert: @jakebixbyavalara I've updated the PR description with the re-exports.

I think it's reasonable to move the `--in-repo-addon` internals to use the `--in` internal functionality. This would definitely clean the code up a bit. I'm open to taking out the deprecation warning in favor of moving to that approach. Does that sound good? 
jakebixbyavalara: @scalvert Yeah, that sounds great.
jakebixbyavalara: @scalvert I meant to ask earlier, but is there a reason your PR is going against `ember-cli:beta` instead of `ember-cli:master`?

Patch:
diff --git a/blueprints/addon-import/index.js b/blueprints/addon-import/index.js
index 1a91854e3..317ac2b38 100644
--- a/blueprints/addon-import/index.js
+++ b/blueprints/addon-import/index.js
@@ -32,6 +32,9 @@ module.exports = {
         if (options.inRepoAddon) {
           return path.join('lib', options.inRepoAddon, 'app');
         }
+        if (options.in) {
+          return path.join(options.in, 'app');
+        }
         return 'app';
       },
     };
diff --git a/lib/commands/destroy.js b/lib/commands/destroy.js
index 31498cecf..1902bfe9b 100644
--- a/lib/commands/destroy.js
+++ b/lib/commands/destroy.js
@@ -1,5 +1,6 @@
 'use strict';
 
+const path = require('path');
 const Command = require('../models/command');
 const Promise = require('rsvp').Promise;
 const mergeBlueprintOptions = require('../utilities/merge-blueprint-options');
@@ -19,6 +20,8 @@ module.exports = Command.extend({
     { name: 'classic',       type: Boolean, default: false, aliases: ['c'] },
     { name: 'dummy',         type: Boolean, default: false, aliases: ['dum', 'id'] },
     { name: 'in-repo-addon', type: String,  default: null,  aliases: ['in-repo', 'ir'] },
+    { name: 'in',            type: String,  default: null, description: 'Runs a blueprint against an in repo addon. ' +
+                                                                        'A path is expected, relative to the root of the project.' },
   ],
 
   anonymousOptions: [
@@ -51,6 +54,11 @@ module.exports = Command.extend({
       commandOptions.pod = !commandOptions.pod;
     }
 
+    if (commandOptions.in) {
+      let relativePath = path.relative(this.project.root, commandOptions.in);
+      commandOptions.in = path.resolve(relativePath);
+    }
+
     let taskOptions = merge(taskArgs, commandOptions || {});
 
     if (this.project.initializeAddons) {
diff --git a/lib/commands/generate.js b/lib/commands/generate.js
index 08a5b1797..244ca59a5 100644
--- a/lib/commands/generate.js
+++ b/lib/commands/generate.js
@@ -1,5 +1,6 @@
 'use strict';
 
+const path = require('path');
 const chalk = require('chalk');
 const Command = require('../models/command');
 const Promise = require('rsvp').Promise;
@@ -22,6 +23,8 @@ module.exports = Command.extend({
     { name: 'classic',       type: Boolean, default: false, aliases: ['c'] },
     { name: 'dummy',         type: Boolean, default: false, aliases: ['dum', 'id'] },
     { name: 'in-repo-addon', type: String,  default: null,  aliases: ['in-repo', 'ir'] },
+    { name: 'in',            type: String,  default: null, description: 'Runs a blueprint against an in repo addon. ' +
+                                                                        'A path is expected, relative to the root of the project.' },
   ],
 
   anonymousOptions: [
@@ -55,6 +58,11 @@ module.exports = Command.extend({
       }
     }
 
+    if (commandOptions.in) {
+      let relativePath = path.relative(this.project.root, commandOptions.in);
+      commandOptions.in = path.resolve(relativePath);
+    }
+
     let taskOptions = _.merge(taskArgs, commandOptions || {});
 
     if (this.project.initializeAddons) {
diff --git a/lib/models/blueprint.js b/lib/models/blueprint.js
index d527701b5..c41f0da61 100644
--- a/lib/models/blueprint.js
+++ b/lib/models/blueprint.js
@@ -385,14 +385,26 @@ let Blueprint = CoreObject.extend({
   /**
     @private
     @method _checkInRepoAddonExists
-    @param {String} inRepoAddon
+    @param {Object} options
   */
-  _checkInRepoAddonExists(inRepoAddon) {
-    if (inRepoAddon) {
-      let addon = findAddonByName(this.project, inRepoAddon);
+  _checkInRepoAddonExists(options) {
+    let addon;
+
+    if (options.inRepoAddon) {
+      addon = findAddonByName(this.project, options.inRepoAddon);
+
+      if (!addon) {
+        throw new SilentError(`You specified the 'in-repo-addon' flag, but the ` +
+          `in-repo-addon '${options.inRepoAddon}' does not exist. Please check the name and try again.`);
+      }
+    }
+
+    if (options.in) {
+      addon = findAddonByPath(this.project, options.in);
+
       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.`);
+        throw new SilentError(`You specified the 'in' flag, but the ` +
+          `in repo addon '${options.in}' does not exist. Please check the name and try again.`);
       }
     }
   },
@@ -438,7 +450,7 @@ let Blueprint = CoreObject.extend({
 
     this._normalizeEntityName(options.entity);
     this._checkForPod(options.verbose);
-    this._checkInRepoAddonExists(options.inRepoAddon);
+    this._checkInRepoAddonExists(options);
 
     logger.info('START: processing blueprint: `%s`', this.name);
     let start = new Date();
@@ -599,6 +611,10 @@ let Blueprint = CoreObject.extend({
           let relativeAddonPath = path.relative(project.root, addon.root);
           return path.join(relativeAddonPath, 'addon');
         }
+        if (options.in) {
+          let relativeAddonPath = path.relative(project.root, options.in);
+          return path.join(relativeAddonPath, 'addon');
+        }
         if (options.inDummy) {
           return path.join('tests', 'dummy', 'app');
         }
@@ -800,6 +816,7 @@ let Blueprint = CoreObject.extend({
       hasPathToken: this.hasPathToken,
       inAddon,
       inRepoAddon: options.inRepoAddon,
+      in: options.in,
       inDummy,
       blueprintName: this.name,
       originBlueprintName,
@@ -1496,6 +1513,10 @@ function findAddonByName(addonOrProject, name) {
   return addonOrProject.addons.find(addon => findAddonByName(addon, name));
 }
 
+function findAddonByPath(project, addonPath) {
+  return project.addons.find(addon => addon.root === addonPath);
+}
+
 /**
   @private
   @method isValidFile


Test patch:
diff --git a/tests/acceptance/in-option-destroy-test.js b/tests/acceptance/in-option-destroy-test.js
new file mode 100644
index 000000000..c09a59e07
--- /dev/null
+++ b/tests/acceptance/in-option-destroy-test.js
@@ -0,0 +1,113 @@
+'use strict';
+
+const co = require('co');
+const RSVP = require('rsvp');
+const ember = require('../helpers/ember');
+const fs = require('fs-extra');
+const path = require('path');
+let remove = RSVP.denodeify(fs.remove);
+let root = process.cwd();
+let tmproot = path.join(root, 'tmp');
+const mkTmpDirIn = require('../../lib/utilities/mk-tmp-dir-in');
+const initApp = require('../helpers/init-app');
+const generateUtils = require('../helpers/generate-utils');
+
+const Blueprint = require('../../lib/models/blueprint');
+const BlueprintNpmTask = require('ember-cli-internal-test-helpers/lib/helpers/disable-npm-on-blueprint');
+
+const chai = require('../chai');
+let expect = chai.expect;
+let file = chai.file;
+
+describe('Acceptance: ember destroy with --in option', function() {
+  let tmpdir;
+
+  this.timeout(20000);
+
+  before(function() {
+    BlueprintNpmTask.disableNPM(Blueprint);
+  });
+
+  after(function() {
+    BlueprintNpmTask.restoreNPM(Blueprint);
+  });
+
+  beforeEach(co.wrap(function *() {
+    tmpdir = yield mkTmpDirIn(tmproot);
+    process.chdir(tmpdir);
+  }));
+
+  afterEach(function() {
+    this.timeout(10000);
+
+    process.chdir(root);
+    return remove(tmproot);
+  });
+
+  function generate(args) {
+    let generateArgs = ['generate'].concat(args);
+    return ember(generateArgs);
+  }
+
+  function destroy(args) {
+    let destroyArgs = ['destroy'].concat(args);
+    return ember(destroyArgs);
+  }
+
+  function assertFilesExist(files) {
+    files.forEach(function(f) {
+      expect(file(f)).to.exist;
+    });
+  }
+
+  function assertFilesNotExist(files) {
+    files.forEach(function(f) {
+      expect(file(f)).to.not.exist;
+    });
+  }
+
+  const assertDestroyAfterGenerate = co.wrap(function *(args, addonPath, files) {
+    yield initApp();
+    yield generateUtils.inRepoAddon(addonPath);
+    yield generateUtils.tempBlueprint();
+    yield generate(args);
+
+    assertFilesExist(files);
+
+    let result = yield destroy(args);
+    expect(result, 'destroy command did not exit with errorCode').to.be.an('object');
+    assertFilesNotExist(files);
+  });
+
+  it('blueprint foo --in lib/other-thing', function() {
+    let addonPath = './lib/other-thing';
+    let commandArgs = ['foo', 'bar', '--in', addonPath];
+    let files = ['lib/other-thing/addon/foos/bar.js'];
+
+    return assertDestroyAfterGenerate(commandArgs, addonPath, files);
+  });
+
+  it('blueprint foo --in ./non-lib/other-thing', function() {
+    let addonPath = './non-lib/other-thing';
+    let commandArgs = ['foo', 'bar', '--in', addonPath];
+    let files = ['non-lib/other-thing/addon/foos/bar.js'];
+
+    return assertDestroyAfterGenerate(commandArgs, addonPath, files);
+  });
+
+  it('blueprint foo --in non-lib/other-thing', function() {
+    let addonPath = 'non-lib/other-thing';
+    let commandArgs = ['foo', 'bar', '--in', addonPath];
+    let files = ['non-lib/other-thing/addon/foos/bar.js'];
+
+    return assertDestroyAfterGenerate(commandArgs, addonPath, files);
+  });
+
+  it('blueprint foo --in non-lib/nested/other-thing', function() {
+    let addonPath = 'non-lib/nested/other-thing';
+    let commandArgs = ['foo', 'bar', '--in', addonPath];
+    let files = ['non-lib/nested/other-thing/addon/foos/bar.js'];
+
+    return assertDestroyAfterGenerate(commandArgs, addonPath, files);
+  });
+});
diff --git a/tests/acceptance/in-option-generate-test.js b/tests/acceptance/in-option-generate-test.js
new file mode 100644
index 000000000..9135e0ed5
--- /dev/null
+++ b/tests/acceptance/in-option-generate-test.js
@@ -0,0 +1,97 @@
+'use strict';
+
+const RSVP = require('rsvp');
+const ember = require('../helpers/ember');
+const fs = require('fs-extra');
+const path = require('path');
+let remove = RSVP.denodeify(fs.remove);
+let root = process.cwd();
+let tmproot = path.join(root, 'tmp');
+const Blueprint = require('../../lib/models/blueprint');
+const BlueprintNpmTask = require('ember-cli-internal-test-helpers/lib/helpers/disable-npm-on-blueprint');
+const mkTmpDirIn = require('../../lib/utilities/mk-tmp-dir-in');
+const initApp = require('../helpers/init-app');
+const generateUtils = require('../helpers/generate-utils');
+
+const chai = require('../chai');
+let expect = chai.expect;
+let file = chai.file;
+
+describe('Acceptance: ember generate with --in option', function() {
+  this.timeout(20000);
+
+  before(function() {
+    BlueprintNpmTask.disableNPM(Blueprint);
+  });
+
+  after(function() {
+    BlueprintNpmTask.restoreNPM(Blueprint);
+  });
+
+  beforeEach(function() {
+    return mkTmpDirIn(tmproot).then(function(tmpdir) {
+      process.chdir(tmpdir);
+    });
+  });
+
+  afterEach(function() {
+    process.chdir(root);
+    return remove(tmproot);
+  });
+
+  it('generate blueprint foo using lib', function() {
+    // build an app with an in-repo addon in a non-standard path
+    return initApp()
+      .then(() => generateUtils.inRepoAddon('lib/other-thing'))
+      // generate in project blueprint to allow easier testing of in-repo generation
+      .then(() => generateUtils.tempBlueprint())
+      // confirm that we can generate into the non-lib path
+      .then(() =>
+        ember(['generate', 'foo', 'bar', '--in=lib/other-thing']).then(function() {
+          expect(file('lib/other-thing/addon/foos/bar.js')).to.exist;
+        })
+      );
+  });
+
+  it('generate blueprint foo using custom path using current directory', function() {
+    // build an app with an in-repo addon in a non-standard path
+    return initApp()
+      .then(() => generateUtils.inRepoAddon('./non-lib/other-thing'))
+      // generate in project blueprint to allow easier testing of in-repo generation
+      .then(() => generateUtils.tempBlueprint())
+      // confirm that we can generate into the non-lib path
+      .then(() =>
+        ember(['generate', 'foo', 'bar', '--in=non-lib/other-thing']).then(function() {
+          expect(file('non-lib/other-thing/addon/foos/bar.js')).to.exist;
+        })
+      );
+  });
+
+  it('generate blueprint foo using custom path', function() {
+    // build an app with an in-repo addon in a non-standard path
+    return initApp()
+      .then(() => generateUtils.inRepoAddon('./non-lib/other-thing'))
+      // generate in project blueprint to allow easier testing of in-repo generation
+      .then(() => generateUtils.tempBlueprint())
+      // confirm that we can generate into the non-lib path
+      .then(() =>
+        ember(['generate', 'foo', 'bar', '--in=./non-lib/other-thing']).then(function() {
+          expect(file('non-lib/other-thing/addon/foos/bar.js')).to.exist;
+        })
+      );
+  });
+
+  it('generate blueprint foo using custom nested path', function() {
+    // build an app with an in-repo addon in a non-standard path
+    return initApp()
+      .then(() => generateUtils.inRepoAddon('./non-lib/nested/other-thing'))
+      // generate in project blueprint to allow easier testing of in-repo generation
+      .then(() => generateUtils.tempBlueprint())
+      // confirm that we can generate into the non-lib path
+      .then(() =>
+        ember(['generate', 'foo', 'bar', '--in=./non-lib/nested/other-thing']).then(function() {
+          expect(file('non-lib/nested/other-thing/addon/foos/bar.js')).to.exist;
+        })
+      );
+  });
+});
diff --git a/tests/fixtures/help/generate-blueprint.txt b/tests/fixtures/help/generate-blueprint.txt
index 9dd8ce540..eedb8048a 100644
--- a/tests/fixtures/help/generate-blueprint.txt
+++ b/tests/fixtures/help/generate-blueprint.txt
@@ -15,6 +15,7 @@ ember generate \u001b[33m<blueprint>\u001b[39m \u001b[36m<options...>\u001b[39m
     \u001b[90maliases: -dum, -id\u001b[39m
   \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m
     \u001b[90maliases: --in-repo <value>, -ir <value>\u001b[39m
+  \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.
 
       blueprint \u001b[33m<name>\u001b[39m
         \u001b[90mGenerates a blueprint and definition.\u001b[39m
diff --git a/tests/fixtures/help/generate-with-addon.txt b/tests/fixtures/help/generate-with-addon.txt
index 20ab120b3..66cfd0039 100644
--- a/tests/fixtures/help/generate-with-addon.txt
+++ b/tests/fixtures/help/generate-with-addon.txt
@@ -15,6 +15,7 @@ ember generate \u001b[33m<blueprint>\u001b[39m \u001b[36m<options...>\u001b[39m
     \u001b[90maliases: -dum, -id\u001b[39m
   \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m
     \u001b[90maliases: --in-repo <value>, -ir <value>\u001b[39m
+  \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.
 
 
   Available blueprints:
diff --git a/tests/fixtures/help/help-with-addon.txt b/tests/fixtures/help/help-with-addon.txt
index bcfb455f3..e2611a7aa 100644
--- a/tests/fixtures/help/help-with-addon.txt
+++ b/tests/fixtures/help/help-with-addon.txt
@@ -53,6 +53,7 @@ ember destroy \u001b[33m<blueprint>\u001b[39m \u001b[36m<options...>\u001b[39m
     \u001b[90maliases: -dum, -id\u001b[39m
   \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m
     \u001b[90maliases: --in-repo <value>, -ir <value>\u001b[39m
+  \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.
 
 ember generate \u001b[33m<blueprint>\u001b[39m \u001b[36m<options...>\u001b[39m
   Generates new code from blueprints.
@@ -69,6 +70,7 @@ ember generate \u001b[33m<blueprint>\u001b[39m \u001b[36m<options...>\u001b[39m
     \u001b[90maliases: -dum, -id\u001b[39m
   \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m
     \u001b[90maliases: --in-repo <value>, -ir <value>\u001b[39m
+  \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.
 
 ember help \u001b[33m<command-name (Default: all)>\u001b[39m \u001b[36m<options...>\u001b[39m
   Outputs the usage instructions for all commands or the provided command
diff --git a/tests/fixtures/help/help.js b/tests/fixtures/help/help.js
index eaa32d9a1..2db529be7 100644
--- a/tests/fixtures/help/help.js
+++ b/tests/fixtures/help/help.js
@@ -190,6 +190,13 @@ module.exports = {
           aliases: ['in-repo', 'ir'],
           key: 'inRepoAddon',
           required: false
+        },
+        {
+          name: 'in',
+          default: null,
+          description: 'Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.',
+          key: 'in',
+          required: false
         }
       ],
       anonymousOptions: ['<blueprint>']
@@ -241,6 +248,13 @@ module.exports = {
           aliases: ['in-repo', 'ir'],
           key: 'inRepoAddon',
           required: false
+        },
+        {
+          name: 'in',
+          default: null,
+          description: 'Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.',
+          key: 'in',
+          required: false
         }
       ],
       anonymousOptions: ['<blueprint>'],
diff --git a/tests/fixtures/help/help.txt b/tests/fixtures/help/help.txt
index 67065c7c7..ac16ffb62 100644
--- a/tests/fixtures/help/help.txt
+++ b/tests/fixtures/help/help.txt
@@ -53,6 +53,7 @@ ember destroy \u001b[33m<blueprint>\u001b[39m \u001b[36m<options...>\u001b[39m
     \u001b[90maliases: -dum, -id\u001b[39m
   \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m
     \u001b[90maliases: --in-repo <value>, -ir <value>\u001b[39m
+  \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.
 
 ember generate \u001b[33m<blueprint>\u001b[39m \u001b[36m<options...>\u001b[39m
   Generates new code from blueprints.
@@ -69,6 +70,7 @@ ember generate \u001b[33m<blueprint>\u001b[39m \u001b[36m<options...>\u001b[39m
     \u001b[90maliases: -dum, -id\u001b[39m
   \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m
     \u001b[90maliases: --in-repo <value>, -ir <value>\u001b[39m
+  \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.
 
 ember help \u001b[33m<command-name (Default: all)>\u001b[39m \u001b[36m<options...>\u001b[39m
   Outputs the usage instructions for all commands or the provided command
diff --git a/tests/fixtures/help/with-addon-blueprints.js b/tests/fixtures/help/with-addon-blueprints.js
index b8625c97b..afd328594 100644
--- a/tests/fixtures/help/with-addon-blueprints.js
+++ b/tests/fixtures/help/with-addon-blueprints.js
@@ -190,6 +190,13 @@ module.exports = {
           aliases: ['in-repo', 'ir'],
           key: 'inRepoAddon',
           required: false
+        },
+        {
+          name: 'in',
+          default: null,
+          description: 'Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.',
+          key: 'in',
+          required: false
         }
       ],
       anonymousOptions: ['<blueprint>']
@@ -241,6 +248,13 @@ module.exports = {
           aliases: ['in-repo', 'ir'],
           key: 'inRepoAddon',
           required: false
+        },
+        {
+          name: 'in',
+          default: null,
+          description: 'Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.',
+          key: 'in',
+          required: false
         }
       ],
       anonymousOptions: ['<blueprint>'],
diff --git a/tests/fixtures/help/with-addon-commands.js b/tests/fixtures/help/with-addon-commands.js
index 6b0e8aba3..b812296cb 100644
--- a/tests/fixtures/help/with-addon-commands.js
+++ b/tests/fixtures/help/with-addon-commands.js
@@ -190,6 +190,13 @@ module.exports = {
           aliases: ['in-repo', 'ir'],
           key: 'inRepoAddon',
           required: false
+        },
+        {
+          name: 'in',
+          default: null,
+          description: 'Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.',
+          key: 'in',
+          required: false
         }
       ],
       anonymousOptions: ['<blueprint>']
@@ -241,6 +248,13 @@ module.exports = {
           aliases: ['in-repo', 'ir'],
           key: 'inRepoAddon',
           required: false
+        },
+        {
+          name: 'in',
+          default: null,
+          description: 'Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.',
+          key: 'in',
+          required: false
         }
       ],
       anonymousOptions: ['<blueprint>'],
diff --git a/tests/helpers/generate-utils.js b/tests/helpers/generate-utils.js
new file mode 100644
index 000000000..88d7f02aa
--- /dev/null
+++ b/tests/helpers/generate-utils.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const RSVP = require('rsvp');
+const fs = require('fs-extra');
+let outputFile = RSVP.denodeify(fs.outputFile);
+const ember = require('./ember');
+
+function inRepoAddon(path) {
+  return ember([
+    'generate',
+    'in-repo-addon',
+    path,
+  ]);
+}
+
+function tempBlueprint() {
+  return outputFile(
+    'blueprints/foo/files/__root__/foos/__name__.js',
+    '/* whoah, empty foo! */'
+  );
+}
+
+module.exports = {
+  inRepoAddon,
+  tempBlueprint,
+};
diff --git a/tests/helpers/init-app.js b/tests/helpers/init-app.js
new file mode 100644
index 000000000..a0450d0a2
--- /dev/null
+++ b/tests/helpers/init-app.js
@@ -0,0 +1,14 @@
+'use strict';
+
+const ember = require('./ember');
+
+function initApp() {
+  return ember([
+    'init',
+    '--name=my-app',
+    '--skip-npm',
+    '--skip-bower',
+  ]);
+}
+
+module.exports = initApp;
diff --git a/tests/integration/models/blueprint-test.js b/tests/integration/models/blueprint-test.js
index 6c4b94641..2a03e68c0 100644
--- a/tests/integration/models/blueprint-test.js
+++ b/tests/integration/models/blueprint-test.js
@@ -1476,6 +1476,7 @@ describe('Blueprint', function() {
         dasherizedModuleName: 'mock-project',
         hasPathToken: undefined,
         inAddon: false,
+        in: undefined,
         inDummy: false,
         inRepoAddon: undefined,
         locals: {},
@@ -1558,6 +1559,16 @@ describe('Blueprint', function() {
       expect(result).to.eql(expectation);
     });
 
+    it('should include an in flag of true if options.in is true', function() {
+      options.in = true;
+
+      expectation.in = true;
+
+      result = blueprint._generateFileMapVariables(moduleName, locals, options);
+
+      expect(result).to.eql(expectation);
+    });
+
     it('should have a hasPathToken flag of true if the blueprint hasPathToken is true', function() {
       blueprint.hasPathToken = true;