Search code examples
javascriptnode.jsyeomanyeoman-generator

Invoked Yeoman sub generator doesn't prompt


I would like to invoke some subgenerators from my main generator and have them use a prompt to get their own information. My current implementation does the writing step of the main generator at the same time as the prompting step of the invoked subgenerator, but I would like to do the steps as following:

  1. Prompt step of main generator
  2. Prompt step of invoked subgenerators
  3. Write step of main generator
  4. Write step of invoked subgenerators

My main generator looks like this:

'use strict';
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var yosay = require('yosay');

module.exports = yeoman.generators.Base.extend({
  initializing: function () {
    this.pkg = require('../package.json');
  },

  prompting: function () {
    var done = this.async();

    // Have Yeoman greet the user.
    this.log(yosay(
      'Welcome to the neat ' + chalk.red('DockerSetup') + ' generator!'
    ));

    // Check for usage of redis, postgres and mysql subgenerators
    this.prompt([
    {
      type: 'input',
      name: 'subApplications',
      message: 'Enter the names of the sub-apps comma seperated'
    }], function (props) {
      this.subApplications = props.subApplications.length ? props.subApplications.split(',') : [];


    // Run subgenerators

    this.subApplications.forEach(function(name) {
      this.composeWith('docker-setup:sub-application', { args: [name] });
    }.bind(this));

      done();
    }.bind(this));
  },

  writing: function () {
    this.fs.copyTpl(
      this.templatePath('_Readme.md'),
      this.destinationPath('Readme.md')
    );
  }
});

And this is my sub-generator

'use strict';
var yeoman = require('yeoman-generator');

module.exports = yeoman.generators.NamedBase.extend({
  initializing: function () {
    this.log('You called the DockerSetup subgenerator with the argument ' + this.name + '.');
  },

  prompting: function () {
    // Assume that the sub-apps are one level under this with same name

    this.prompt([
    {
      type: 'list',
      name: 'mainTech',
      message: 'Which is the main technology used?',
      choices: ['rails', 'yii', 'frontend']
    }, {
      type: 'checkbox',
      name: 'additionalTechnologies',
      message: 'Which technologies are used in this subapp?',
      choices: ['redis', 'postgres', 'mysql']
    }], function (props) {
      this.mainTech = props.mainTech;
      this.additionalTechnologies = props.additionalTechnologies;


      // This is done here, because if it's in the writing part it gets called before the prompt
      var path = this.destinationPath('fig.yml'),
          file = this.readFileAsString(path),
          content;

      switch(this.mainTech) {
        case 'rails':
          content = 'content';
        break;

        case 'yii':
        break;

        case 'frontend':
        break;
      }

      this.additionalTechnologies.forEach(function (tech) {
        content += ('    - ' + tech);
      });

      file += content;
      this.write(path, file);

      done();
    }.bind(this));
  }
});

Solution

  • You shouldn't call the done() function until you're actually done with the prompting within that subgenerator. As it is, you're giving yo the go-ahead to continue execution right after dispatching the work to the subgenerator. Instead, you should call done() only asynchronously (as it is the use-case most of the time with async/done).

    For this, I believe you can chain the composeWith command with .on as such:

    this.subApplications.forEach(function(name) {
          this.composeWith('docker-setup:sub-application', { args: [name] })
              .on('end',function(){
                            done();
                        });
        }.bind(this));
    

    (The 'end' event is emitted at the end of every yo process, as per base.js line 358)