Repository: jhipster/generator-jhipster
Pull request #9937 (2019-06-18 16:12:57)

Support multiple blueprints
Add support for multiple blueprints as discussed in https://github.com/jhipster/generator-jhipster-blueprint/issues/31.

Here are the key points of the change:
- Added new option `--blueprints` which takes a comma separated list of blueprints, e.g. using cli:  `jhipster --force --no-insight --skip-checks --with-entities --from-cli --blueprints kotlin,vuejs`. The reason for not using the existing `blueprint` is that I have also removed the `blueprintVersion` from `yo-rc.json` which is mainly used by the upgrade sub-gen and converted the blueprint config property from a simple string to an array. The `blueprint` option and config property are still read in order to preserve backwards compatibility but in the case of the app sub-gen a warning is emitted to switch to using `blueprints` instead. e.g: So now instead of having:
```
        "blueprint": "generator-jhipster-kotlin",
        "blueprintVersion": "0.8.1",
```
we now have:
```
    "blueprints": [
      {
        "name": "generator-jhipster-kotlin",
        "version": "0.8.1"
      },
      {
        "name": "generator-jhipster-vuejs",
        "version": "1.0.0"
      }
    ]
```
- The `blueprint` option is still valid and will remain as is for the cli commands.
- The upgrade sub-gen has been updated to support multiple blueprints as well. The option `target-blueprint-version` has been replaced with `target-blueprint-versions` in which one needs to define both the blueprint name and version sperated by @, e.g in the form `kotlin@0.8.0`. Multiple distinct versions can be supplied here as well.
- Currently the blueprints are composed in their declaration order and they are all executed in sequence if they exists. No checking is done. Its also 

Few more notes:
- Tested with official Kotlin and VueJS blueprints and it seems to work well.(There is an issue with the Kotlin one but will shortly submit a PR to fix it).
- Most of the tests have been intentionally left with the old option `blueprint` to make sure nothing is broken but we can change them and add unit tests to check backwards compatibility if we agree on this change. We could also keep around both the `blueprint` and `blueprints` options and just keep the change in the config file.

Please make sure the below checklist is followed for Pull Requests.

-   [x] [Travis tests](https://travis-ci.org/jhipster/generator-jhipster/pull_requests) are green
-   [x] Tests are added where necessary
-   [ ] Documentation is added/updated where necessary
-   [x] Coding Rules & Commit Guidelines as per our [CONTRIBUTING.md document](https://github.com/jhipster/generator-jhipster/blob/master/CONTRIBUTING.md) are followed


deepu105: Great I'm adding a bounty on this.
deepu105: Let me know when ready and i'll review/test 
pvliss: @deepu105 Well it already works and all unit and CI tests pass. Added in draft mode because its a rather big change and wanted to trigger some discussion first. e.g. do you agree with the `blueprints` flag?
deepu105: Of course, I agree with the flag, and since you added deprecation warning
and backward compatibility its good to go. I reviewed most parts, except
the upgrade and util part will do that as well, feel free to change it to
ready

Thanks & Regards,
Deepu


On Tue, Jun 18, 2019 at 6:38 PM fleboulch <notifications@github.com> wrote:

> *@fleboulch* commented on this pull request.
> ------------------------------
>
> In generators/generator-base.js
> <https://github.com/jhipster/generator-jhipster/pull/9937#discussion_r294918673>
> :
>
> > @@ -341,7 +341,7 @@ module.exports = class extends PrivateBase {
>                      fullPath +
>                      chalk.yellow('. Reference to ')}npm dependency (name: ${name}, version:${version})${chalk.yellow(' not added.\n')}`
>              );
> -            this.debug('Error:', e);
> +            this.dnaebug('Error:', e);
>
> Typo
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/jhipster/generator-jhipster/pull/9937?email_source=notifications&email_token=AAIOKF6IDRA7DGUFNWQHYRDP3EFQRA5CNFSM4HZBVTSKYY3PNVWWK3TUL52HS4DFWFIHK3DMKJSXC5LFON2FEZLWNFSXPKTDN5WW2ZLOORPWSZGOB34VL6Y#pullrequestreview-251221499>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAIOKF5MZ5EKWPVRHP222CDP3EFQRANCNFSM4HZBVTSA>
> .
>


Patch:
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 7efb418ba1..27ad37a22c 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1,2 @@
 open_collective: generator-jhipster
-custom: https://www.jhipster.tech/sponsors/
\ No newline at end of file
+custom: https://www.jhipster.tech/sponsors/
diff --git a/cli/commands.js b/cli/commands.js
index 711096f45a..cd8952375e 100644
--- a/cli/commands.js
+++ b/cli/commands.js
@@ -19,20 +19,37 @@
 const chalk = require('chalk');
 const jhipsterUtils = require('../generators/utils');
 
-let customCommands = {};
-const indexOfBlueprintArgv = process.argv.indexOf('--blueprint');
-if (indexOfBlueprintArgv > -1) {
-    /* eslint-disable import/no-dynamic-require */
-    /* eslint-disable global-require */
+const customCommands = loadBlueprintCommands();
 
-    const blueprint = jhipsterUtils.normalizeBlueprintName(process.argv[indexOfBlueprintArgv + 1]);
-    try {
-        customCommands = require(`${blueprint}/cli/commands`);
-    } catch (e) {
-        const msg = `No custom command found within blueprint: ${blueprint}`;
-        /* eslint-disable no-console */
-        console.info(`${chalk.green.bold('INFO!')} ${msg}`);
+function loadBlueprintCommands() {
+    const blueprintNames = [];
+    const indexOfBlueprintArgv = process.argv.indexOf('--blueprint');
+    if (indexOfBlueprintArgv > -1) {
+        blueprintNames.push(process.argv[indexOfBlueprintArgv + 1]);
     }
+    const indexOfBlueprintsArgv = process.argv.indexOf('--blueprints');
+    if (indexOfBlueprintsArgv > -1) {
+        blueprintNames.push(...process.argv[indexOfBlueprintsArgv + 1].split(','));
+    }
+    let result = {};
+    if (blueprintNames.length > 0) {
+        blueprintNames
+            .filter((v, i, a) => a.indexOf(v) === i)
+            .map(v => jhipsterUtils.normalizeBlueprintName(v))
+            .forEach(blueprint => {
+                /* eslint-disable import/no-dynamic-require */
+                /* eslint-disable global-require */
+                try {
+                    const blueprintCommands = require(`${blueprint}/cli/commands`);
+                    result = { ...result, ...blueprintCommands };
+                } catch (e) {
+                    const msg = `No custom commands found within blueprint: ${blueprint}`;
+                    /* eslint-disable no-console */
+                    console.info(`${chalk.green.bold('INFO!')} ${msg}`);
+                }
+            });
+    }
+    return result;
 }
 
 const defaultCommands = {
diff --git a/generators/app/index.js b/generators/app/index.js
index f5fb9937e6..8a420c5e58 100644
--- a/generators/app/index.js
+++ b/generators/app/index.js
@@ -156,9 +156,15 @@ module.exports = class extends BaseGenerator {
             type: String
         });
 
-        // This adds support for a `--blueprint` flag which can be used to specify a blueprint to use for generation
+        // NOTE: Deprecated!!! Use --blueprints instead
         this.option('blueprint', {
-            desc: 'Specify a generator blueprint to use for the sub generators',
+            desc: 'DEPRECATED: Specify a generator blueprint to use for the sub generators',
+            type: String
+        });
+        // This adds support for a `--blueprints` flag which can be used to specify one or more blueprints to use for generation
+        this.option('blueprints', {
+            desc:
+                'A comma separated list of one or more generator blueprints to use for the sub generators, e.g. --blueprints kotlin,vuejs',
             type: String
         });
 
@@ -189,8 +195,24 @@ module.exports = class extends BaseGenerator {
 
         this.withEntities = this.options['with-entities'];
         this.skipChecks = this.options['skip-checks'];
-        const blueprint = jhipsterUtils.normalizeBlueprintName(this.options.blueprint || this.config.get('blueprint'));
-        this.blueprint = this.configOptions.blueprint = blueprint;
+
+        let blueprints = this.options.blueprints || '';
+        // check for old single blueprint declaration
+        const blueprint = this.options.blueprint;
+        if (blueprint) {
+            this.warning('--blueprint option is deprecated. Please use --blueprints instead');
+            if (!blueprints.split(',').includes(blueprint)) {
+                blueprints = `${blueprint},${blueprints}`;
+            }
+        }
+        if (blueprints) {
+            blueprints = jhipsterUtils.parseBluePrints(blueprints);
+        } else {
+            blueprints = jhipsterUtils.loadBlueprintsFromConfiguration(this);
+        }
+
+        this.blueprints = this.configOptions.blueprints = blueprints;
+
         this.useNpm = this.configOptions.useNpm = !this.options.yarn;
         this.useYarn = !this.useNpm;
 
@@ -210,8 +232,8 @@ module.exports = class extends BaseGenerator {
             },
 
             validateBlueprint() {
-                if (this.blueprint && !this.skipChecks) {
-                    this.checkBlueprint(this.blueprint);
+                if (this.blueprints && !this.skipChecks) {
+                    this.blueprints.forEach(e => this.checkBlueprint(e));
                 }
             },
 
@@ -254,11 +276,14 @@ module.exports = class extends BaseGenerator {
                     this.jhipsterVersion = this.config.get('jhipsterVersion');
                 }
                 this.otherModules = this.config.get('otherModules') || [];
-                if (this.blueprint) {
-                    this.blueprintVersion = this.findBlueprintVersion(this.blueprint);
+                if (this.blueprints && this.blueprints.length > 0) {
+                    this.blueprints.forEach(blueprint => {
+                        blueprint.version = this.findBlueprintVersion(blueprint.name) || 'latest';
+                    });
+
                     // Remove potential previous value to avoid duplicates
-                    this.otherModules = this.otherModules.filter(module => module.name !== this.blueprint);
-                    this.otherModules.push({ name: this.blueprint, version: this.blueprintVersion || 'latest' });
+                    this.otherModules = this.otherModules.filter(module => this.blueprints.findIndex(bp => bp.name === module.name) === -1);
+                    this.otherModules.push(...this.blueprints);
                 }
                 this.testFrameworks = this.config.get('testFrameworks');
                 this.enableTranslation = this.config.get('enableTranslation');
@@ -409,7 +434,7 @@ module.exports = class extends BaseGenerator {
                     config.nativeLanguage = this.nativeLanguage;
                     config.languages = this.languages;
                 }
-                this.blueprint && (config.blueprint = this.blueprint);
+                this.blueprints && (config.blueprints = this.blueprints);
                 this.blueprintVersion && (config.blueprintVersion = this.blueprintVersion);
                 this.reactive && (config.reactive = this.reactive);
                 this.skipClient && (config.skipClient = true);
@@ -492,11 +517,11 @@ module.exports = class extends BaseGenerator {
                         if (code === 0 && this.gitInitialized) {
                             this.gitExec('add -A', { trace: false }, () => {
                                 let commitMsg = `Initial application generated by JHipster-${this.jhipsterVersion}`;
-                                if (this.blueprint) {
-                                    commitMsg += ` with blueprint: ${this.blueprint.replace('generator-jhipster-', '')}`;
-                                    if (this.blueprintVersion) {
-                                        commitMsg += `-${this.blueprintVersion}`;
-                                    }
+                                if (this.blueprints && this.blueprints.length > 0) {
+                                    const bpInfo = this.blueprints
+                                        .map(bp => `${bp.name.replace('generator-jhipster-', '')}-${bp.version}`)
+                                        .join(', ');
+                                    commitMsg += ` with blueprints: ${bpInfo}`;
                                 }
                                 this.gitExec(`commit -am "${commitMsg}"`, { trace: false }, () => {
                                     this.log(chalk.green.bold('Application successfully committed to Git.'));
diff --git a/generators/cleanup.js b/generators/cleanup.js
index d629760f28..33bf41b9ae 100644
--- a/generators/cleanup.js
+++ b/generators/cleanup.js
@@ -190,4 +190,8 @@ function cleanupOldServerFiles(generator, javaDir, testDir, mainResourceDir, tes
         generator.removeFolder(`${CLIENT_MAIN_SRC_DIR}app/shared/layout/header/menus`);
         generator.removeFolder(`${CLIENT_TEST_SRC_DIR}spec/app/shared/layout/header/menus`);
     }
+    if (generator.isJhipsterVersionLessThan('6.1.0')) {
+        generator.config.delete('blueprint');
+        generator.config.delete('blueprintVersion');
+    }
 }
diff --git a/generators/client/index.js b/generators/client/index.js
index 72ef33a091..fbe8f50a5b 100644
--- a/generators/client/index.js
+++ b/generators/client/index.js
@@ -27,7 +27,7 @@ const packagejs = require('../../package.json');
 const constants = require('../generator-constants');
 const statistics = require('../statistics');
 
-let useBlueprint;
+let useBlueprints;
 
 module.exports = class extends BaseBlueprintGenerator {
     constructor(args, opts) {
@@ -62,16 +62,8 @@ module.exports = class extends BaseBlueprintGenerator {
         });
 
         this.setupClientOptions(this);
-        const blueprint = this.options.blueprint || this.configOptions.blueprint || this.config.get('blueprint');
-        // use global variable since getters dont have access to instance property
-        if (!opts.fromBlueprint) {
-            useBlueprint = this.composeBlueprint(blueprint, 'client', {
-                ...this.options,
-                configOptions: this.configOptions
-            });
-        } else {
-            useBlueprint = false;
-        }
+
+        useBlueprints = !opts.fromBlueprint && this.instantiateBlueprints('client');
     }
 
     // Public API method used by the getter and also by Blueprints
@@ -178,7 +170,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get initializing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._initializing();
     }
 
@@ -201,7 +193,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get prompting() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._prompting();
     }
 
@@ -273,7 +265,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get configuring() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._configuring();
     }
 
@@ -357,7 +349,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get default() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._default();
     }
 
@@ -368,16 +360,16 @@ module.exports = class extends BaseBlueprintGenerator {
                 if (this.skipClient) return;
                 switch (this.clientFramework) {
                     case 'react':
-                        return writeReactFiles.call(this, useBlueprint);
+                        return writeReactFiles.call(this, useBlueprints);
                     default:
-                        return writeAngularFiles.call(this, useBlueprint);
+                        return writeAngularFiles.call(this, useBlueprints);
                 }
             }
         };
     }
 
     get writing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._writing();
     }
 
@@ -409,7 +401,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get install() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._install();
     }
 
@@ -431,7 +423,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get end() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._end();
     }
 };
diff --git a/generators/common/index.js b/generators/common/index.js
index 5819d3b345..93f2a8f3c9 100644
--- a/generators/common/index.js
+++ b/generators/common/index.js
@@ -22,7 +22,7 @@ const writeFiles = require('./files').writeFiles;
 const prettierConfigFiles = require('./files').prettierConfigFiles;
 const constants = require('../generator-constants');
 
-let useBlueprint;
+let useBlueprints;
 
 module.exports = class extends BaseBlueprintGenerator {
     constructor(args, opts) {
@@ -38,17 +38,8 @@ module.exports = class extends BaseBlueprintGenerator {
 
         this.setupServerOptions(this);
         this.setupClientOptions(this);
-        const blueprint = this.options.blueprint || this.configOptions.blueprint || this.config.get('blueprint');
-        if (!opts.fromBlueprint) {
-            // use global variable since getters dont have access to instance property
-            useBlueprint = this.composeBlueprint(blueprint, 'common', {
-                ...this.options,
-                'client-hook': !this.skipClient,
-                configOptions: this.configOptions
-            });
-        } else {
-            useBlueprint = false;
-        }
+
+        useBlueprints = !opts.fromBlueprint && this.instantiateBlueprints('common', { 'client-hook': !this.skipClient });
     }
 
     // Public API method used by the getter and also by Blueprints
@@ -76,7 +67,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get initializing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._initializing();
     }
 
@@ -100,7 +91,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get default() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._default();
     }
 
@@ -110,7 +101,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get writing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._writing();
     }
 };
diff --git a/generators/entity-client/index.js b/generators/entity-client/index.js
index 0f7959cce3..dda20aac8f 100644
--- a/generators/entity-client/index.js
+++ b/generators/entity-client/index.js
@@ -22,7 +22,7 @@ const writeFiles = require('./files').writeFiles;
 const utils = require('../utils');
 const BaseBlueprintGenerator = require('../generator-base-blueprint');
 
-let useBlueprint;
+let useBlueprints;
 
 module.exports = class extends BaseBlueprintGenerator {
     constructor(args, opts) {
@@ -30,18 +30,10 @@ module.exports = class extends BaseBlueprintGenerator {
         utils.copyObjectProps(this, opts.context);
         this.jhipsterContext = opts.jhipsterContext || opts.context;
         this.configOptions = opts.configOptions || {};
-        const blueprint = this.options.blueprint || this.configOptions.blueprint || this.config.get('blueprint');
-        if (!opts.fromBlueprint) {
-            // use global variable since getters dont have access to instance property
-            useBlueprint = this.composeBlueprint(blueprint, 'entity-client', {
-                ...this.options,
-                context: opts.context,
-                debug: opts.context.isDebugEnabled,
-                configOptions: this.configOptions
-            });
-        } else {
-            useBlueprint = false;
-        }
+
+        useBlueprints =
+            !opts.fromBlueprint &&
+            this.instantiateBlueprints('entity-client', { context: opts.context, debug: opts.context.isDebugEnabled });
     }
 
     // Public API method used by the getter and also by Blueprints
@@ -50,7 +42,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get writing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._writing();
     }
 
@@ -67,7 +59,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get end() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._end();
     }
 };
diff --git a/generators/entity-i18n/index.js b/generators/entity-i18n/index.js
index 939bdddbc3..1e30bd026c 100644
--- a/generators/entity-i18n/index.js
+++ b/generators/entity-i18n/index.js
@@ -22,7 +22,7 @@ const utils = require('../utils');
 const BaseBlueprintGenerator = require('../generator-base-blueprint');
 
 /* constants used throughout */
-let useBlueprint;
+let useBlueprints;
 
 module.exports = class extends BaseBlueprintGenerator {
     constructor(args, opts) {
@@ -30,18 +30,9 @@ module.exports = class extends BaseBlueprintGenerator {
         utils.copyObjectProps(this, opts.context);
         this.jhipsterContext = opts.jhipsterContext || opts.context;
         this.configOptions = opts.configOptions || {};
-        const blueprint = this.options.blueprint || this.configOptions.blueprint || this.config.get('blueprint');
-        if (!opts.fromBlueprint) {
-            // use global variable since getters dont have access to instance property
-            useBlueprint = this.composeBlueprint(blueprint, 'entity-i18n', {
-                ...this.options,
-                context: opts.context,
-                debug: opts.context.isDebugEnabled,
-                configOptions: this.configOptions
-            });
-        } else {
-            useBlueprint = false;
-        }
+
+        useBlueprints =
+            !opts.fromBlueprint && this.instantiateBlueprints('entity-i18n', { context: opts.context, debug: opts.context.isDebugEnabled });
     }
 
     // Public API method used by the getter and also by Blueprints
@@ -50,7 +41,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get writing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._writing();
     }
 };
diff --git a/generators/entity-server/index.js b/generators/entity-server/index.js
index 75a7318722..9bd887c8ae 100644
--- a/generators/entity-server/index.js
+++ b/generators/entity-server/index.js
@@ -22,7 +22,7 @@ const utils = require('../utils');
 const BaseBlueprintGenerator = require('../generator-base-blueprint');
 
 /* constants used throughout */
-let useBlueprint;
+let useBlueprints;
 
 module.exports = class extends BaseBlueprintGenerator {
     constructor(args, opts) {
@@ -33,18 +33,10 @@ module.exports = class extends BaseBlueprintGenerator {
         if (this.databaseType === 'cassandra') {
             this.pkType = 'UUID';
         }
-        const blueprint = this.options.blueprint || this.configOptions.blueprint || this.config.get('blueprint');
-        if (!opts.fromBlueprint) {
-            // use global variable since getters dont have access to instance property
-            useBlueprint = this.composeBlueprint(blueprint, 'entity-server', {
-                ...this.options,
-                context: opts.context,
-                debug: opts.context.isDebugEnabled,
-                configOptions: this.configOptions
-            });
-        } else {
-            useBlueprint = false;
-        }
+
+        useBlueprints =
+            !opts.fromBlueprint &&
+            this.instantiateBlueprints('entity-server', { context: opts.context, debug: opts.context.isDebugEnabled });
     }
 
     // Public API method used by the getter and also by Blueprints
@@ -53,7 +45,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get writing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._writing();
     }
 };
diff --git a/generators/entity/index.js b/generators/entity/index.js
index 940c5eaf9e..ef97e2dee6 100644
--- a/generators/entity/index.js
+++ b/generators/entity/index.js
@@ -29,7 +29,7 @@ const statistics = require('../statistics');
 
 /* constants used throughout */
 const SUPPORTED_VALIDATION_RULES = constants.SUPPORTED_VALIDATION_RULES;
-let useBlueprint;
+let useBlueprints;
 
 class EntityGenerator extends BaseBlueprintGenerator {
     constructor(args, opts) {
@@ -124,17 +124,8 @@ class EntityGenerator extends BaseBlueprintGenerator {
 
         this.setupEntityOptions(this, this, this.context);
         this.registerPrettierTransform();
-        const blueprint = this.options.blueprint || this.configOptions.blueprint || this.config.get('blueprint');
-        if (!opts.fromBlueprint) {
-            // use global variable since getters dont have access to instance property
-            useBlueprint = this.composeBlueprint(blueprint, 'entity', {
-                ...this.options,
-                configOptions: this.configOptions,
-                arguments: [this.context.name]
-            });
-        } else {
-            useBlueprint = false;
-        }
+
+        useBlueprints = !opts.fromBlueprint && this.instantiateBlueprints('entity', { arguments: [this.context.name] });
     }
 
     // Public API method used by the getter and also by Blueprints
@@ -336,7 +327,7 @@ class EntityGenerator extends BaseBlueprintGenerator {
     }
 
     get initializing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._initializing();
     }
 
@@ -360,7 +351,7 @@ class EntityGenerator extends BaseBlueprintGenerator {
     }
 
     get prompting() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._prompting();
     }
 
@@ -1088,7 +1079,7 @@ class EntityGenerator extends BaseBlueprintGenerator {
     }
 
     get configuring() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._configuring();
     }
 
@@ -1146,7 +1137,7 @@ class EntityGenerator extends BaseBlueprintGenerator {
     }
 
     get writing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._writing();
     }
 
@@ -1185,7 +1176,7 @@ class EntityGenerator extends BaseBlueprintGenerator {
     }
 
     get install() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._install();
     }
 }
diff --git a/generators/generator-base-blueprint.js b/generators/generator-base-blueprint.js
index 532bab2d31..5f202115d5 100644
--- a/generators/generator-base-blueprint.js
+++ b/generators/generator-base-blueprint.js
@@ -17,6 +17,7 @@
  * limitations under the License.
  */
 /* eslint-disable consistent-return */
+const jhipsterUtils = require('./utils');
 const BaseGenerator = require('./generator-base');
 
 /**
@@ -59,4 +60,39 @@ module.exports = class extends BaseGenerator {
     _end() {
         return {};
     }
+
+    /**
+     * Instantiates the blueprint generators, if any.
+     * @param {string} subGen - sub generator
+     * @param {any} extraOptions - extra options to pass to blueprint generator
+     * @return {true} useBlueprints - true if one or more blueprints generators have been constructed; false otherwise
+     */
+    instantiateBlueprints(subGen, extraOptions) {
+        let useBlueprints = false;
+
+        const blueprints = jhipsterUtils.parseBluePrints(
+            this.options.blueprints ||
+                this.configOptions.blueprints ||
+                this.config.get('blueprints') ||
+                this.options.blueprint ||
+                this.configOptions.blueprint ||
+                this.config.get('blueprint')
+        );
+        if (blueprints && blueprints.length > 0) {
+            blueprints.forEach(blueprint => {
+                let bpOptions = {
+                    ...this.options,
+                    configOptions: this.configOptions
+                };
+                if (extraOptions) {
+                    bpOptions = { ...bpOptions, ...extraOptions };
+                }
+                const useBP = this.composeBlueprint(blueprint.name, subGen, bpOptions);
+                if (!useBlueprints && useBP) {
+                    useBlueprints = true;
+                }
+            });
+        }
+        return useBlueprints;
+    }
 };
diff --git a/generators/languages/index.js b/generators/languages/index.js
index a89a107227..cf20ec88dd 100644
--- a/generators/languages/index.js
+++ b/generators/languages/index.js
@@ -25,7 +25,7 @@ const statistics = require('../statistics');
 
 const constants = require('../generator-constants');
 
-let useBlueprint;
+let useBlueprints;
 
 module.exports = class extends BaseBlueprintGenerator {
     constructor(args, opts) {
@@ -78,18 +78,10 @@ module.exports = class extends BaseBlueprintGenerator {
                 }
             });
         }
-        const blueprint = this.options.blueprint || this.configOptions.blueprint || this.config.get('blueprint');
-        // use global variable since getters dont have access to instance property
-        if (!opts.fromBlueprint) {
-            useBlueprint = this.composeBlueprint(blueprint, 'languages', {
-                ...this.options,
-                languages: this.languages,
-                configOptions: this.configOptions,
-                arguments: this.options.languages
-            });
-        } else {
-            useBlueprint = false;
-        }
+
+        useBlueprints =
+            !opts.fromBlueprint &&
+            this.instantiateBlueprints('languages', { languages: this.languages, arguments: this.options.languages });
     }
 
     // Public API method used by the getter and also by Blueprints
@@ -134,7 +126,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get initializing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._initializing();
     }
 
@@ -146,7 +138,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get prompting() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._prompting();
     }
 
@@ -163,7 +155,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get configuring() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._configuring();
     }
 
@@ -212,7 +204,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get default() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._default();
     }
 
@@ -250,7 +242,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get writing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._writing();
     }
 };
diff --git a/generators/server/index.js b/generators/server/index.js
index 6fb4403a2b..01ad39bbdd 100644
--- a/generators/server/index.js
+++ b/generators/server/index.js
@@ -28,7 +28,7 @@ const constants = require('../generator-constants');
 const statistics = require('../statistics');
 const { getBase64Secret, getRandomHex } = require('../utils');
 
-let useBlueprint;
+let useBlueprints;
 
 module.exports = class extends BaseBlueprintGenerator {
     constructor(args, opts) {
@@ -59,17 +59,9 @@ module.exports = class extends BaseBlueprintGenerator {
         this.uaaBaseName = this.options.uaaBaseName || this.configOptions.uaaBaseName || this.config.get('uaaBaseName');
 
         this.setupServerOptions(this);
-        const blueprint = this.options.blueprint || this.configOptions.blueprint || this.config.get('blueprint');
-        if (!opts.fromBlueprint) {
-            // use global variable since getters dont have access to instance property
-            useBlueprint = this.composeBlueprint(blueprint, 'server', {
-                ...this.options,
-                'client-hook': !this.skipClient,
-                configOptions: this.configOptions
-            });
-        } else {
-            useBlueprint = false;
-        }
+
+        useBlueprints = !opts.fromBlueprint && this.instantiateBlueprints('server', { 'client-hook': !this.skipClient });
+
         this.registerPrettierTransform();
     }
 
@@ -298,7 +290,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get initializing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._initializing();
     }
 
@@ -338,7 +330,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get prompting() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._prompting();
     }
 
@@ -417,7 +409,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get configuring() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._configuring();
     }
 
@@ -460,7 +452,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get default() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._default();
     }
 
@@ -470,7 +462,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get writing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._writing();
     }
 
@@ -493,7 +485,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get install() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._install();
     }
 
@@ -525,7 +517,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get end() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._end();
     }
 };
diff --git a/generators/spring-controller/index.js b/generators/spring-controller/index.js
index 844cda8838..cba7daa76a 100644
--- a/generators/spring-controller/index.js
+++ b/generators/spring-controller/index.js
@@ -27,7 +27,7 @@ const statistics = require('../statistics');
 const SERVER_MAIN_SRC_DIR = constants.SERVER_MAIN_SRC_DIR;
 const SERVER_TEST_SRC_DIR = constants.SERVER_TEST_SRC_DIR;
 
-let useBlueprint;
+let useBlueprints;
 
 module.exports = class extends BaseBlueprintGenerator {
     constructor(args, opts) {
@@ -48,17 +48,7 @@ module.exports = class extends BaseBlueprintGenerator {
         });
         this.defaultOption = this.options.default;
 
-        const blueprint = this.options.blueprint || this.configOptions.blueprint || this.config.get('blueprint');
-        if (!opts.fromBlueprint) {
-            // use global variable since getters dont have access to instance property
-            useBlueprint = this.composeBlueprint(blueprint, 'spring-controller', {
-                ...this.options,
-                arguments: [this.name],
-                configOptions: this.configOptions
-            });
-        } else {
-            useBlueprint = false;
-        }
+        useBlueprints = !opts.fromBlueprint && this.instantiateBlueprints('spring-controller', { arguments: [this.name] });
     }
 
     // Public API method used by the getter and also by Blueprints
@@ -89,7 +79,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get initializing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._initializing();
     }
 
@@ -101,7 +91,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get prompting() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._prompting();
     }
 
@@ -115,7 +105,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get default() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._default();
     }
 
@@ -178,7 +168,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get writing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._writing();
     }
 };
diff --git a/generators/spring-service/index.js b/generators/spring-service/index.js
index c600659373..08e3e19b0d 100644
--- a/generators/spring-service/index.js
+++ b/generators/spring-service/index.js
@@ -24,7 +24,7 @@ const statistics = require('../statistics');
 
 const SERVER_MAIN_SRC_DIR = constants.SERVER_MAIN_SRC_DIR;
 
-let useBlueprint;
+let useBlueprints;
 module.exports = class extends BaseBlueprintGenerator {
     constructor(args, opts) {
         super(args, opts);
@@ -44,17 +44,7 @@ module.exports = class extends BaseBlueprintGenerator {
         });
         this.defaultOption = this.options.default;
 
-        const blueprint = this.options.blueprint || this.configOptions.blueprint || this.config.get('blueprint');
-        if (!opts.fromBlueprint) {
-            // use global variable since getters dont have access to instance property
-            useBlueprint = this.composeBlueprint(blueprint, 'spring-service', {
-                ...this.options,
-                arguments: [this.name],
-                configOptions: this.configOptions
-            });
-        } else {
-            useBlueprint = false;
-        }
+        useBlueprints = !opts.fromBlueprint && this.instantiateBlueprints('spring-service', { arguments: [this.name] });
     }
 
     // Public API method used by the getter and also by Blueprints
@@ -76,7 +66,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get initializing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._initializing();
     }
 
@@ -106,7 +96,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get prompting() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._prompting();
     }
 
@@ -120,7 +110,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get default() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._default();
     }
 
@@ -149,7 +139,7 @@ module.exports = class extends BaseBlueprintGenerator {
     }
 
     get writing() {
-        if (useBlueprint) return;
+        if (useBlueprints) return;
         return this._writing();
     }
 };
diff --git a/generators/upgrade/index.js b/generators/upgrade/index.js
index 03ada0425b..1bcf96b095 100644
--- a/generators/upgrade/index.js
+++ b/generators/upgrade/index.js
@@ -25,6 +25,7 @@ const gitignore = require('parse-gitignore');
 const BaseGenerator = require('../generator-base');
 const constants = require('../generator-constants');
 const statistics = require('../statistics');
+const utils = require('../utils');
 
 /* Constants used throughout */
 const GENERATOR_JHIPSTER = 'generator-jhipster';
@@ -48,9 +49,9 @@ module.exports = class extends BaseGenerator {
             desc: 'Upgrade to a specific version instead of the latest',
             type: String
         });
-        // This adds support for a `--target-blueprint-version` flag
-        this.option('target-blueprint-version', {
-            desc: 'Upgrade to a specific blueprint version instead of the latest',
+        // This adds support for a `--target-blueprint-versions` flag
+        this.option('target-blueprint-versions', {
+            desc: 'Upgrade to specific blueprint versions instead of the latest, e.g. --target-blueprint-versions foo@0.0.1,bar@1.0.2',
             type: String
         });
 
@@ -69,7 +70,7 @@ module.exports = class extends BaseGenerator {
         });
 
         this.targetJhipsterVersion = this.options['target-version'];
-        this.targetBlueprintVersion = this.options['target-blueprint-version'];
+        this.targetBlueprintVersions = utils.parseBluePrints(this.options['target-blueprint-versions']);
         this.skipInstall = this.options['skip-install'];
         this.silent = this.options.silent;
     }
@@ -88,8 +89,7 @@ module.exports = class extends BaseGenerator {
             loadConfig() {
                 this.config = this.getAllJhipsterConfig(this, true);
                 this.currentJhipsterVersion = this.config.get('jhipsterVersion');
-                this.blueprint = this.config.get('blueprint');
-                this.currentBlueprintVersion = this.config.get('blueprintVersion');
+                this.blueprints = utils.loadBlueprintsFromConfiguration(this);
                 this.clientPackageManager = this.config.get('clientPackageManager');
                 this.clientFramework = this.config.get('clientFramework');
             }
@@ -116,8 +116,7 @@ module.exports = class extends BaseGenerator {
         this.success('Cleaned up project directory');
     }
 
-    _generate(jhipsterVersion, blueprintVersion, callback) {
-        const blueprintInfo = this.blueprint ? ` and ${this.blueprint} ${blueprintVersion}` : '';
+    _generate(jhipsterVersion, blueprintInfo, callback) {
         this.log(`Regenerating application with JHipster ${jhipsterVersion}${blueprintInfo}...`);
         let generatorCommand = 'yo jhipster';
         if (semver.gte(jhipsterVersion, FIRST_CLI_SUPPORTED_VERSION)) {
@@ -154,12 +153,11 @@ module.exports = class extends BaseGenerator {
         });
     }
 
-    _regenerate(jhipsterVersion, blueprintVersion, callback) {
-        this._generate(jhipsterVersion, blueprintVersion, () => {
+    _regenerate(jhipsterVersion, blueprintInfo, callback) {
+        this._generate(jhipsterVersion, blueprintInfo, () => {
             const keystore = `${SERVER_MAIN_RES_DIR}config/tls/keystore.p12`;
             this.info(`Removing ${keystore}`);
             shelljs.rm('-Rf', keystore);
-            const blueprintInfo = this.blueprint ? ` and ${this.blueprint} ${blueprintVersion}` : '';
             this._gitCommitAll(`Generated with JHipster ${jhipsterVersion}${blueprintInfo}`, () => {
                 callback();
             });
@@ -211,34 +209,58 @@ module.exports = class extends BaseGenerator {
                 });
             },
 
-            checkLatestBlueprintVersion() {
-                if (!this.blueprint) {
-                    this.log('No blueprint detected, skipping check of last blueprint version');
-                    return;
-                }
-                if (!this.currentBlueprintVersion) {
-                    this.error('No blueprintVersion defined in .yo-rc.json, unable to upgrade');
-                }
-                if (this.targetBlueprintVersion) {
-                    this.log(`Upgrading to the target blueprint version: ${this.targetBlueprintVersion}`);
-                    this.latestBlueprintVersion = this.targetBlueprintVersion;
+            checkLatestBlueprintVersions() {
+                if (!this.blueprints || this.blueprints.length < 0) {
+                    this.log('No blueprints detected, skipping check of last blueprint version');
                     return;
                 }
+
+                this.success('Checking for new blueprint versions');
                 const done = this.async();
-                this._retrieveLatestVersion(this.blueprint, latestVersion => {
-                    this.latestBlueprintVersion = latestVersion;
-                    if (semver.lt(this.currentBlueprintVersion, this.latestBlueprintVersion)) {
-                        this.newBlueprintVersionFound = true;
-                        this.success(`New ${this.blueprint} version found: ${this.latestBlueprintVersion}`);
-                    } else if (this.force) {
-                        this.newBlueprintVersionFound = true;
-                        this.log(chalk.yellow('Forced re-generation'));
-                    } else {
-                        this.newBlueprintVersionFound = false;
-                        this.warning(
-                            `${chalk.green('No update available.')} Application has already been generated with latest blueprint version.`
-                        );
-                    }
+                Promise.all(
+                    this.blueprints
+                        .filter(blueprint => {
+                            if (this.targetBlueprintVersions && this.targetBlueprintVersions.length > 0) {
+                                const targetBlueprint = this.targetBlueprintVersions.find(elem => {
+                                    return elem.name === blueprint.name;
+                                });
+                                if (targetBlueprint && targetBlueprint.version && targetBlueprint.version !== 'latest') {
+                                    this.log(
+                                        `Blueprint ${targetBlueprint.name} will be upgraded to target version: ${targetBlueprint.version}`
+                                    );
+                                    blueprint.latestBlueprintVersion = targetBlueprint.version;
+                                    return false;
+                                }
+                            }
+                            return true;
+                        })
+                        .map(blueprint => {
+                            return new Promise(resolve => {
+                                this._retrieveLatestVersion(blueprint.name, latestVersion => {
+                                    blueprint.latestBlueprintVersion = latestVersion;
+                                    if (semver.lt(blueprint.version, blueprint.latestBlueprintVersion)) {
+                                        this.newBlueprintVersionFound = true;
+                                        this.success(`New ${blueprint.name} version found: ${this.latestBlueprintVersion}`);
+                                    } else if (this.force) {
+                                        this.newBlueprintVersionFound = true;
+                                        this.log(chalk.yellow('Forced re-generation'));
+                                    } else {
+                                        if (this.newBlueprintVersionFound === undefined) {
+                                            this.newBlueprintVersionFound = false;
+                                        }
+                                        this.warning(
+                                            `${chalk.green(
+                                                'No update available.'
+                                            )} Application has already been generated with latest version for blueprint: ${blueprint.name}`
+                                        );
+                                    }
+                                    this.success(`Done checking for new version for blueprint ${blueprint.name}`);
+                                    resolve();
+                                });
+                            });
+                        })
+                ).then(() => {
+                    this.success('Done checking for new version of blueprints');
                     done();
                 });
             },
@@ -339,20 +361,38 @@ module.exports = class extends BaseGenerator {
                     this._installNpmPackageLocally(GENERATOR_JHIPSTER, version, callback);
                 };
 
-                const installBlueprintLocally = (version, callback) => {
-                    if (!this.blueprint) {
+                const installBlueprintsLocally = callback => {
+                    if (!this.blueprints || this.blueprints.length < 1) {
                         this.log('Skipping local blueprint installation since no blueprint has been detected');
                         callback();
                         return;
                     }
-                    this._installNpmPackageLocally(this.blueprint, version, callback);
+
+                    this.success('Installing blueprints locally...');
+                    Promise.all(
+                        this.blueprints.map(blueprint => {
+                            return new Promise(resolve => {
+                                this._installNpmPackageLocally(blueprint.name, blueprint.version, () => {
+                                    this.success(`Done installing blueprint: ${blueprint.name}@${blueprint.version}`);
+                                    resolve();
+                                });
+                            });
+                        })
+                    ).then(() => {
+                        this.success('Done installing blueprints locally');
+                        callback();
+                    });
                 };
 
                 const regenerate = () => {
                     this._cleanUp();
                     installJhipsterLocally(this.currentJhipsterVersion, () => {
-                        installBlueprintLocally(this.currentBlueprintVersion, () => {
-                            this._regenerate(this.currentJhipsterVersion, this.currentBlueprintVersion, () => {
+                        installBlueprintsLocally(() => {
+                            const blueprintInfo =
+                                this.blueprints && this.blueprints.length > 0
+                                    ? ` and ${this.blueprints.map(bp => bp.name + bp.version).join(', ')} `
+                                    : '';
+                            this._regenerate(this.currentJhipsterVersion, blueprintInfo, () => {
                                 this._gitCheckout(this.sourceBranch, () => {
                                     // consider code up-to-date
                                     recordCodeHasBeenGenerated();
@@ -394,19 +434,37 @@ module.exports = class extends BaseGenerator {
                 this._installNpmPackageLocally(GENERATOR_JHIPSTER, this.latestJhipsterVersion, done);
             },
 
-            updateBlueprint() {
-                if (!this.blueprint) {
+            updateBlueprints() {
+                if (!this.blueprints || this.blueprints.length < 1) {
                     this.log('Skipping blueprint update since no blueprint has been detected');
                     return;
                 }
+
+                this.success('Upgrading blueprints...');
                 const done = this.async();
-                this._installNpmPackageLocally(this.blueprint, this.latestBlueprintVersion, done);
+                Promise.all(
+                    this.blueprints.map(blueprint => {
+                        return new Promise(resolve => {
+                            this._installNpmPackageLocally(blueprint.name, blueprint.latestBlueprintVersion, () => {
+                                this.success(`Done upgrading blueprint ${blueprint.name} to version ${blueprint.latestBlueprintVersion}`);
+                                resolve();
+                            });
+                        });
+                    })
+                ).then(() => {
+                    this.success('Done upgrading blueprints');
+                    done();
+                });
             },
 
             generateWithLatestVersion() {
                 const done = this.async();
                 this._cleanUp();
-                this._regenerate(this.latestJhipsterVersion, this.latestBlueprintVersion, done);
+
+                const blueprintInfo = this.blueprints
+                    ? ` and ${this.blueprints.map(bp => bp.name + bp.latestBlueprintVersion).join(', ')} `
+                    : '';
+                this._regenerate(this.latestJhipsterVersion, blueprintInfo, done);
             },
 
             checkoutSourceBranch() {
@@ -422,6 +480,7 @@ module.exports = class extends BaseGenerator {
                     done();
                 });
             },
+
             checkConflictsInPackageJson() {
                 const done = this.async();
                 this.gitExec(['diff', '--name-only', '--diff-filter=U', 'package.json'], { silent: this.silent }, (code, msg, err) => {
diff --git a/generators/utils.js b/generators/utils.js
index c632130b35..4e4fac8388 100644
--- a/generators/utils.js
+++ b/generators/utils.js
@@ -47,6 +47,8 @@ module.exports = {
     getBase64Secret,
     getRandomHex,
     checkStringInFile,
+    loadBlueprintsFromConfiguration,
+    parseBluePrints,
     normalizeBlueprintName
 };
 
@@ -422,9 +424,21 @@ function getAllJhipsterConfig(generator, force, basePath = '') {
     if ((force || !configuration.baseName) && jhiCore.FileUtils.doesFileExist(filePath)) {
         const yoRc = JSON.parse(fs.readFileSync(filePath, { encoding: 'utf-8' }));
         configuration = yoRc['generator-jhipster'];
-        // merge the blueprint config if available
-        if (configuration.blueprint) {
-            configuration = { ...configuration, ...yoRc[configuration.blueprint] };
+
+        // merge the blueprint configs if available
+        const oldBlueprintName = configuration.blueprint;
+        const blueprints = configuration.blueprints || [];
+        if (oldBlueprintName && blueprints.indexOf(oldBlueprintName) === -1) {
+            const oldBlueprintVersion = configuration.blueprintVersion || 'latest';
+            blueprints.push({
+                name: oldBlueprintName,
+                version: oldBlueprintVersion
+            });
+        }
+        const blueprintConfigs = blueprints.map(bp => yoRc[bp.name]).filter(el => el !== null && el !== undefined);
+        if (blueprintConfigs.length > 0) {
+            const mergedConfigs = Object.assign(...blueprintConfigs);
+            configuration = { ...configuration, ...mergedConfigs };
         }
     }
     if (!configuration.get || typeof configuration.get !== 'function') {
@@ -472,9 +486,63 @@ function checkStringInFile(path, search, generator) {
     return fileContent.includes(search);
 }
 
+/**
+ * Loads the blueprint information from the configuration of the specified generator.
+ * @param config - the generator's configuration object.
+ * @returns {Array} an array that contains the info for each blueprint
+ */
+function loadBlueprintsFromConfiguration(generator) {
+    // load blueprints from config file
+    const blueprints = generator.config.get('blueprints') || [];
+
+    const oldBlueprintName = generator.config.get('blueprint');
+    if (oldBlueprintName && blueprints.findIndex(e => e.name === oldBlueprintName) === -1) {
+        const version = generator.config.get('blueprintVersion') || 'latest';
+        blueprints.push(parseBlueprintInfo(`${oldBlueprintName}@${version}`));
+    }
+    return blueprints;
+}
+
+/**
+ * Splits and normalizes a comma separated list of blueprint names with optional versions.
+ * @param {string} blueprints - comma separated list of blueprint names, e.g kotlin,vuewjs@1.0.1. If an array then
+ * no processing is performed and it is returned as is.
+ * @returns {Array} an array that contains the info for each blueprint
+ */
+function parseBluePrints(blueprints) {
+    if (blueprints && !Array.isArray(blueprints)) {
+        return blueprints
+            .split(',')
+            .filter(el => el != null && el.length > 0)
+            .map(blueprint => parseBlueprintInfo(blueprint));
+    }
+    return blueprints;
+}
+
+/**
+ * Normalize blueprint name if needed and also extracts version if defined. If no version is defined then `latest`
+ * is used by default.
+ * @param {string} blueprint - name of the blueprint and optionally a version, e.g kotlin[@0.8.1]
+ * @returns {object} containing the name and version of the blueprint
+ */
+function parseBlueprintInfo(blueprint) {
+    let bpName = normalizeBlueprintName(blueprint);
+    let version = 'latest';
+    const idx = bpName.lastIndexOf('@');
+    if (idx > 0) {
+        version = bpName.slice(idx + 1);
+        bpName = bpName.slice(0, idx);
+    }
+    return {
+        name: bpName,
+        version
+    };
+}
+
 /**
  * Normalize blueprint name: prepend 'generator-jhipster-' if needed
  * @param {string} blueprint - name of the blueprint
+ * @returns {string} the normalized blueprint name
  */
 function normalizeBlueprintName(blueprint) {
     if (blueprint && blueprint.startsWith('@')) {


Test patch:
diff --git a/test/blueprint/app-blueprint.spec.js b/test/blueprint/app-blueprint.spec.js
index 2ceeda46fc..14af723803 100644
--- a/test/blueprint/app-blueprint.spec.js
+++ b/test/blueprint/app-blueprint.spec.js
@@ -60,7 +60,9 @@ describe('JHipster application generator with blueprint', () => {
         });
 
         it('blueprint version is saved in .yo-rc.json', () => {
-            assert.fileContent('.yo-rc.json', /"blueprintVersion": "9.9.9"/);
+            assert.JSONFileContent('.yo-rc.json', {
+                'generator-jhipster': { blueprints: [{ name: 'generator-jhipster-myblueprint', version: '9.9.9' }] }
+            });
         });
         it('blueprint module and version are in package.json', () => {
             assert.fileContent('package.json', /"generator-jhipster-myblueprint": "9.9.9"/);
diff --git a/test/blueprint/multiple-blueprints.spec.js b/test/blueprint/multiple-blueprints.spec.js
new file mode 100644
index 0000000000..aa3b178c59
--- /dev/null
+++ b/test/blueprint/multiple-blueprints.spec.js
@@ -0,0 +1,162 @@
+const path = require('path');
+const assert = require('yeoman-assert');
+const helpers = require('yeoman-test');
+const expectedFiles = require('../utils/expected-files');
+const ClientGenerator = require('../../generators/client');
+const ServerGenerator = require('../../generators/server');
+
+const mockClientBlueprintSubGen = class extends ClientGenerator {
+    constructor(args, opts) {
+        super(args, Object.assign({ fromBlueprint: true }, opts)); // fromBlueprint variable is important
+    }
+
+    get initializing() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._initializing();
+    }
+
+    get prompting() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._prompting();
+    }
+
+    get configuring() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._configuring();
+    }
+
+    get default() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._default();
+    }
+
+    get writing() {
+        const phaseFromJHipster = super._writing();
+        const customPhaseSteps = {
+            addDummyProperty() {
+                this.addNpmDependency('dummy-blueprint-property', '2.0');
+            }
+        };
+        return {
+            ...phaseFromJHipster,
+            ...customPhaseSteps
+        };
+    }
+
+    get install() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._install();
+    }
+
+    get end() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._end();
+    }
+};
+
+const mockServerBlueprintSubGen = class extends ServerGenerator {
+    constructor(args, opts) {
+        super(args, Object.assign({ fromBlueprint: true }, opts)); // fromBlueprint variable is important
+    }
+
+    get initializing() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._initializing();
+    }
+
+    get writing() {
+        const phaseFromJHipster = super._writing();
+        const customPhaseSteps = {
+            addDummyProperty() {
+                this.addMavenProperty('dummy-blueprint-property', 'foo');
+            }
+        };
+        return {
+            ...phaseFromJHipster,
+            ...customPhaseSteps
+        };
+    }
+
+    get prompting() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._prompting();
+    }
+
+    get configuring() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._configuring();
+    }
+
+    get default() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._default();
+    }
+
+    get install() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._install();
+    }
+
+    get end() {
+        // Here we are not overriding this phase and hence its being handled by JHipster
+        return super._end();
+    }
+};
+
+describe('JHipster entity generator with multiple blueprints', () => {
+    const blueprintNames = [
+        'generator-jhipster-my-client-blueprint,generator-jhipster-my-server-blueprint',
+        'my-client-blueprint,my-server-blueprint'
+    ];
+
+    blueprintNames.forEach(blueprints => {
+        describe(`generate entity with multiple blueprints option '${blueprints}'`, () => {
+            before(done => {
+                helpers
+                    .run(path.join(__dirname, '../../generators/app'))
+                    .withOptions({
+                        'from-cli': true,
+                        skipInstall: true,
+                        skipChecks: true,
+                        blueprints
+                    })
+                    .withGenerators([
+                        [mockClientBlueprintSubGen, 'jhipster-my-client-blueprint:client'],
+                        [mockServerBlueprintSubGen, 'jhipster-my-server-blueprint:server']
+                    ])
+                    .withPrompts({
+                        baseName: 'jhipster',
+                        clientFramework: 'angularX',
+                        packageName: 'com.mycompany.myapp',
+                        packageFolder: 'com/mycompany/myapp',
+                        serviceDiscoveryType: false,
+                        authenticationType: 'jwt',
+                        cacheProvider: 'ehcache',
+                        enableHibernateCache: true,
+                        databaseType: 'sql',
+                        devDatabaseType: 'h2Memory',
+                        prodDatabaseType: 'mysql',
+                        enableTranslation: true,
+                        nativeLanguage: 'en',
+                        languages: ['fr']
+                    })
+                    .on('end', done);
+            });
+
+            it('creates expected files from jhipster app generator', () => {
+                assert.file(expectedFiles.common);
+                assert.file(expectedFiles.server);
+                assert.file(expectedFiles.maven);
+                assert.file(expectedFiles.client);
+            });
+
+            it('contains the specific change added by the client blueprint', () => {
+                assert.fileContent('package.json', /dummy-blueprint-property/);
+            });
+
+            it('contains the specific change added by the server blueprint', () => {
+                assert.fileContent('pom.xml', /dummy-blueprint-property/);
+            });
+        });
+    });
+});
diff --git a/test/blueprint/scoped-blueprint.spec.js b/test/blueprint/scoped-blueprint.spec.js
index 1f1f0f3e2f..fca3d75caf 100644
--- a/test/blueprint/scoped-blueprint.spec.js
+++ b/test/blueprint/scoped-blueprint.spec.js
@@ -60,7 +60,9 @@ describe('JHipster application generator with scoped blueprint', () => {
         });
 
         it('blueprint version is saved in .yo-rc.json', () => {
-            assert.fileContent('.yo-rc.json', /"blueprintVersion": "9.9.9"/);
+            assert.JSONFileContent('.yo-rc.json', {
+                'generator-jhipster': { blueprints: [{ name: '@jhipster/generator-jhipster-scoped-blueprint', version: '9.9.9' }] }
+            });
         });
         it('blueprint module and version are in package.json', () => {
             assert.fileContent('package.json', /"@jhipster\/generator-jhipster-scoped-blueprint": "9.9.9"/);
diff --git a/test/cli/cli.spec.js b/test/cli/cli.spec.js
index eb1e1169f5..445d08894b 100644
--- a/test/cli/cli.spec.js
+++ b/test/cli/cli.spec.js
@@ -45,8 +45,23 @@ describe('jhipster cli test', () => {
             expect(error).to.not.be.null;
             expect(error.code).to.equal(1);
             /* eslint-disable prettier/prettier */
-            expect(stdout.includes('No custom command found within blueprint')).to.be.true;
-            expect(stderr.includes('is not a known command')).to.be.true;
+            expect(stdout.includes('No custom commands found within blueprint: generator-jhipster-bar')).to.be.true;
+            expect(stderr.includes('foo is not a known command')).to.be.true;
+            done();
+        });
+    });
+
+    it('should delegate to blueprint on multiple blueprints command but will not find it', function(done) {
+        this.timeout(4000);
+
+        exec(`${cmd} foo --blueprints bar,baz`, (error, stdout, stderr) => {
+            console.log(error);
+            expect(error).to.not.be.null;
+            expect(error.code).to.equal(1);
+            /* eslint-disable prettier/prettier */
+            expect(stdout.includes('No custom commands found within blueprint: generator-jhipster-bar')).to.be.true;
+            expect(stdout.includes('No custom commands found within blueprint: generator-jhipster-baz')).to.be.true;
+            expect(stderr.includes('foo is not a known command')).to.be.true;
             done();
         });
     });
diff --git a/test/upgrade.spec.js b/test/upgrade.spec.js
index 7ca02ef770..40159ba87a 100644
--- a/test/upgrade.spec.js
+++ b/test/upgrade.spec.js
@@ -164,8 +164,9 @@ describe('JHipster upgrade generator', function() {
         });
 
         it('still contains blueprint information', () => {
-            assert.fileContent('.yo-rc.json', new RegExp(`"blueprint": "${blueprintName}"`));
-            assert.fileContent('.yo-rc.json', new RegExp(`"blueprintVersion": "${blueprintVersion}"`));
+            assert.JSONFileContent('.yo-rc.json', {
+                'generator-jhipster': { blueprints: [{ name: blueprintName, version: blueprintVersion }] }
+            });
             assert.fileContent('package.json', new RegExp(`"${blueprintName}": "${blueprintVersion}"`));
         });
 
diff --git a/test/utils.spec.js b/test/utils.spec.js
index 4fbb273e70..eafc5b106f 100644
--- a/test/utils.spec.js
+++ b/test/utils.spec.js
@@ -75,6 +75,37 @@ describe('JHipster Utils', () => {
             });
         });
     });
+    describe('::parseBluePrints', () => {
+        it('does nothing if an array', () => {
+            const expected = [{ name: 'generator-jhipster-foo', version: 'latest' }];
+            const actual = utils.parseBluePrints(expected);
+            assert.deepStrictEqual(actual, expected);
+        });
+        it('adds generator-jhipster prefix if it is absent', () => {
+            const expected = [{ name: 'generator-jhipster-foo', version: 'latest' }];
+            const actual = utils.parseBluePrints('foo');
+            assert.deepStrictEqual(actual, expected);
+        });
+        it('keeps generator-jhipster prefix if it is present', () => {
+            const expected = [{ name: 'generator-jhipster-foo', version: '1.0.1' }];
+            const actual = utils.parseBluePrints('generator-jhipster-foo@1.0.1');
+            assert.deepStrictEqual(actual, expected);
+        });
+        it("doesn't modify scoped package and extracts version", () => {
+            const expected = [{ name: '@corp/foo', version: '1.0.1' }];
+            const actual = utils.parseBluePrints('@corp/foo@1.0.1');
+            assert.deepStrictEqual(actual, expected);
+        });
+        it('parses comma separated list', () => {
+            const expected = [
+                { name: 'generator-jhipster-foo', version: 'latest' },
+                { name: 'generator-jhipster-bar', version: '1.0.1' },
+                { name: '@corp/foo', version: 'latest' }
+            ];
+            const actual = utils.parseBluePrints('foo,bar@1.0.1,@corp/foo');
+            assert.deepStrictEqual(actual, expected);
+        });
+    });
     describe('::normalizeBlueprintName', () => {
         it('adds generator-jhipster prefix if it is absent', () => {
             const generatorName = utils.normalizeBlueprintName('foo');