javascriptnode.jstypescriptyeomanyeoman-generator

Yeoman generator add a new file generated exsiting project


I've yeoman generator which generate a simple sproject successfully.

I want that after the project generation, in latter time that the use will have the ability to generate a new file deployment.yaml under the app folder, however it needs to read some data from the main generator for example appName as the sub-generator needs to generate a new file inside the generated application.

e.g. yo tdk

This command generates a new project

And when I run yo tdk:event (or something similar) it will generate a new file inside the project app folder

For illustration I've created this very simple generator

const Generator = require("yeoman-generator");

module.exports = class extends Generator {
  prompting() {
    this.props = {
      appName: "my-app",
      srvName: "my-service"
    };

    const prompts = [
      {
        name: "appName",
        message: "Project name: ",
        type: "input",
        default: this.props.appName
      },
      {
        name: "srvName",
        message: "Service name: ",
        type: "input",
        default: this.props.srvName
      }
    ];

    return this.prompt(prompts).then(props => {
      this.props = props;
    });
  }

  writing() {
    this.fs.copyTpl(
      this.templatePath("app"),
      this.destinationPath(this.props.appName),
      this.props
    );
  }
};

This generator have two simple question

  1. app name
  2. service name

And it will generate a project like

myapp   /root
 -app   /folder
  - service.yaml   /single file at the project generation

The generated service.yaml looks like following:

apiVersion: v1
kind: Service
metadata:
  name: <%= appName %>
spec:
  selector:
    app: <%= srvName %>
  ports:
    - protocol: TCP
      port: 80

Now after the generation of the project with this service.yaml file I want in latter time (after the project generation)to add new file deployment.yaml under the app folder

deployment.yaml

apiVersion: v1
kind: Deployment
metadata:
  name: <%= appName %>  //this is the appname from the project generation

spec:
  replicas: <%= replica %>
  selector:
    app: <%= srvName %>

The appName & srvName are coming from the main generator, (I saw that there is option to share data between sub generator https://yeoman.io/authoring/storage.html , not sure how to share this between generators ) and the replica should come from the new/sub generator

This is the project structure after the generation

myapp   /root
 -app   /folder
  - service.yaml      /single file at the project generation
  - deployment.yaml   / new file added to the project under app folder

Like user start another generator/sub and have a new question e.g. how much replicas do you want? and then generates the file.

How can I do it ?

update This is my project strucutre

myapp
 - node_modules
 - package.json //here I declare the main-generator command -> tdk
 - generators
 -- app
 ---index.ts
 --deployment
 ---index.ts
 ---package.json    //here I declare the sub-generator command -> deploy
 - node_modules
 - package.json
 -.yo-rc.json       //here I see the data that I keep via config.set api

Update

When I call to the sub generator via program like

const yeoman = require('yeoman-environment'); const env = yeoman.createEnv();

env.lookup(function () {
    env.run("tdk:deploy", {
        replicas: 100
    }, (err) => {
        console.log("done", err);
    });
});

I got error:

out from config undefined : undefined //the undefind is from the console in the sub-generator

done TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined
    at validateString (internal/validators.js:125:11)
    at Object.join (path.js:1037:7)

I put a console.log in the subgenerator code like

  initializing() {
    this.srvName = this.config.get("srvName");
    this.appName = this.config.get("appName");
    console.log("out from config", this.srvName, ":", this.appName);
  }

And when I run the subgenerator I got empty config ( from the .yo-rc.json) while checking the .yo-rc.json . I was able to see the entry from the main generator, the data was stored but when I run it from the program it doesnt find it...any idea ?

This is the link for both project (very basic yeoman generator which demonstrate the point) just need to run npm install for both projects and for the generator run also npm link.

At the end: a project should be generated with two files

1. service.yaml   // generated from the main generator
2. deployment.yaml - // generated from sub generator with the properties from the main & sub generator

currently, the deployment.yaml file is not generated

https://drive.google.com/drive/folders/1kBnZxpVcRR9qhGZagVtod7W4wFmt73C6

1 . generator-tdk -  Generator and sub-generator 
2.  yeomanEnv - The code which is running the sub-generator to create the file inside the generated project

What am I doing wrong ? :(

if there is a way from the sub-generator to read the .yo-rc.json , it can help


Solution

  • You can set the values to config inside configuring of the main generator like this:

    configuring() {
      this.config.set('appName', this.props.appName);
      this.config.set('srvName', this.props.srvName);
    }
    

    and read the values inside the sub-generators:

    initializing() {
      this.srvName = this.config.get("srvName");
      this.appName = this.config.get("appName");
    }
    

    So you'll have access to these values via this.srvName and this.appName upon writing.

    Example code:

    app/index.js:

    const Generator = require("yeoman-generator");
    
    module.exports = class extends Generator {
      prompting() {
        this.props = {
          appName: "my-app",
          srvName: "my-service",
        };
    
        const prompts = [
          {
            name: "appName",
            message: "Project name: ",
            type: "input",
            default: this.props.appName,
          },
          {
            name: "srvName",
            message: "Service name: ",
            type: "input",
            default: this.props.srvName,
          },
        ];
    
        return this.prompt(prompts).then((props) => {
          this.props = props;
        });
      }
    
      configuring() {
        this.config.set('appName', this.props.appName);
        this.config.set('srvName', this.props.srvName);
      }
    
      writing() {
        this.fs.copyTpl(
          this.templatePath("app"),
          this.destinationPath(this.props.appName),
          this.props
        );
      }
    };
    

    deploy/index.js:

    const Generator = require("yeoman-generator");
    
    module.exports = class extends Generator {
      initializing() {
        this.srvName = this.config.get("srvName");
        this.appName = this.config.get("appName");
      }
    
      prompting() {
        this.props = {
          replicas: 0,
        };
    
        const prompts = [
          {
            name: "replica",
            message: "how much replicas do you want?",
            type: "input",
            default: this.props.replicas,
          },
        ];
    
        return this.prompt(prompts).then((props) => {
          this.props = props;
        });
      }
    
      writing() {
        this.fs.copyTpl(
          this.templatePath("deploy"),
          this.destinationPath(this.appName),
          {
            srvName: this.srvName,
            appName: this.appName,
            ...this.props,
          }
        );
      }
    };
    
    

    and commands:

    yo <name for the main project generation

    yo <name>:deploy to ask for replicas and create deployment.yaml


    To execute the sub-generator without the use of yo:

    var yeoman = require("yeoman-environment");
    var env = yeoman.createEnv();
    
    env.lookup(function () {
      env.run("<name>:deploy", {
          replicas: 100
      }, (err) => {
        console.log("done", err);
      });
    });
    

    and a sample sub-generator that skips question if values are passed via options (deploy/index.js):

    const Generator = require("yeoman-generator");
    
    module.exports = class extends Generator {
      initializing() {
        this.srvName = this.config.get("srvName");
        this.appName = this.config.get("appName");
      }
    
      prompting() {
        this.props = {
          replicas: 0,
        };
    
        const prompts = [
          {
            name: "replicas",
            message: "which app to generate?",
            type: "input",
            default: this.props.replicas,
            when: !this.options.replicas, // disable the question if it's found in options
          },
        ];
    
        return this.prompt(prompts).then((props) => {
          this.props = props;
    
          // set values from options (if found)
          this.props.replicas = this.options.replicas || this.props.replicas;
        });
      }
    
    
      writing() {
        this.fs.copyTpl(
          this.templatePath("deploy"),
          this.destinationPath(this.appName),
          {
            srvName: this.srvName,
            appName: this.appName,
            ...this.props,
          }
        );
      }
    };