{"id":7876,"date":"2020-07-11T18:59:43","date_gmt":"2020-07-11T18:59:43","guid":{"rendered":"http:\/\/putridparrot.com\/blog\/?p=7876"},"modified":"2020-07-11T18:59:43","modified_gmt":"2020-07-11T18:59:43","slug":"creating-a-yeoman-generator","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/creating-a-yeoman-generator\/","title":{"rendered":"Creating a yeoman generator"},"content":{"rendered":"<p>In the previous post we looked at the basics of getting started with yeoman and the tools etc. around it. Let&#8217;s now write some real code. This is going to be a generator for creating a node server using my preferred stack of technologies and allow the person running it to supply data and\/or options for the generator to allow it to generate code specific to the user&#8217;s needs.<\/p>\n<p><strong>Input<\/strong><\/p>\n<p>Our node server will offer the option of handling REST, websocket or GraphQL endpoints. So we&#8217;re going to need some input from the user to choose the options they want.<\/p>\n<p>First off, DO NOT USE the standard console.log etc. methods for output. Yeoman supplies the log function for this purpose.<\/p>\n<p>Here&#8217;s an example of our generator with some basic interaction<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nvar Generator = require(&quot;yeoman-generator&quot;);\r\nmodule.exports = class extends Generator {\r\n    async prompting() {\r\n        const input = await this.prompt(&#x5B;\r\n            {\r\n                type: &quot;input&quot;,\r\n                name: &quot;name&quot;,\r\n                message: &quot;Enter project name&quot;,\r\n                default: this.appname\r\n            },\r\n            {\r\n                type: &quot;list&quot;,\r\n                name: &quot;endpoint&quot;,\r\n                message: &quot;Endpoint type?&quot;,\r\n                    choices: &#x5B;&quot;REST&quot;, &quot;REST\/websocket&quot;, &quot;GraphQL&quot;]\r\n            }\r\n        ]);\r\n\r\n\r\n        this.log(&quot;Project name: &quot;, input.name);\r\n        this.log(&quot;Endpoint: &quot;, input.endpoint);\r\n    }\r\n};\r\n<\/pre>\n<p>Now if we run our generator we&#8217;ll be promoted (from the CLI) for a project name and for the selected endpoint type. The results of these prompts will then be output to the output stream.<\/p>\n<p>As you can see from this simple example we can now start to build up a list of options for our generator to use when we generate our code.<\/p>\n<p><strong>Command line arguments<\/strong><\/p>\n<p>In some cases we might want to allow the user to supply arguments from the command line, i.e. not be prompted for them. To achieve this we add a constructor, like this<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconstructor(args, opts) {\r\n   super(args, opts);\r\n\r\n   this.argument(&quot;name&quot;, { type: String, required: false });\r\n\r\n   this.log(this.options.name);\r\n}\r\n<\/pre>\n<p>Here&#8217;s we&#8217;ve declared an argument <em>name<\/em> which is not required on the command line, this also allows us to run <em>yo server &#8211;help<\/em> to see a list of options available for our generator.<\/p>\n<p>The only problem with the above code is that if the user supplies this argument, they are still prompted for it via the prompting method. To solve this we can add the following<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nyarn add yeoman-option-or-prompt\r\n<\/pre>\n<p>Now change our code to require yeoman-option-or-prompt, i.e.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nvar OptionOrPrompt = require('yeoman-option-or-prompt');\r\n<\/pre>\n<p>Next change the constructor slightly, to this<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconstructor(args, opts) {\r\n   super(args, opts);\r\n\r\n   this.argument(&quot;name&quot;, { type: String, required: false });\r\n\r\n   this.optionOrPrompt = OptionOrPrompt;\r\n}\r\n<\/pre>\n<p>and finally let&#8217;s change our prompting method to<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nasync prompting() {\r\n\r\n   const input = await this.optionOrPrompt(&#x5B;           \r\n      {\r\n         type: &quot;input&quot;,\r\n         name: &quot;name&quot;,\r\n         message: &quot;Enter project name&quot;,\r\n         default: this.appname\r\n      },\r\n      {\r\n         type: &quot;list&quot;,\r\n         name: &quot;endpoint&quot;,\r\n         message: &quot;Endpoint type?&quot;,\r\n            choices: &#x5B;&quot;REST&quot;, &quot;REST\/websocket&quot;, &quot;GraphQL&quot;]\r\n      }\r\n   ]);\r\n\r\n   this.log(&quot;Project name: &quot;, input.name);\r\n   this.log(&quot;Endpoint: &quot;, input.endpoint);\r\n}\r\n<\/pre>\n<p>Now when we run <em>yo server<\/em> without an argument we still get the project name prompt, but when we supply the argument, i.e. yo server MyProject then the project name prompt no longer appears.<\/p>\n<p><strong>Templates<\/strong><\/p>\n<p>With projects such as the one we&#8217;re developing here, it would be a pain if all output had to be written via code. Luckily yeoman includes a template capability from https:\/\/ejs.co\/. <\/p>\n<p>So in this example add a templates folder to generators\/app and then within it add package.json, here&#8217;s my file<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n{\r\n    &quot;name&quot;: &quot;&lt;%= name %&gt;&quot;,\r\n    &quot;version&quot;: &quot;1.0.0&quot;,\r\n    &quot;description&quot;: &quot;&quot;,\r\n    &quot;module&quot;: &quot;es6&quot;,\r\n    &quot;dependencies&quot;: {\r\n    },\r\n    &quot;devDependencies&quot;: {\r\n    }\r\n  }\r\n<\/pre>\n<p>Notice the use of <%= %> to define our template variables. The variable <em>name<\/em> now needs to be supplied via our generator. We need to make a couple of changes from our original source, the <em>const input<\/em> needs to change to <em>this.input<\/em> to allow the input variable to be accessible in another method, the writing method, which looks like this<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nwriting() {\r\n   this.fs.copyTpl(\r\n      this.templatePath('package.json'),\r\n      this.destinationPath('public\/package.json'),\r\n         { name: this.input.name } \r\n   );\r\n}\r\n<\/pre>\n<p>here&#8217;s the changed prompting method as well<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nasync prompting() {\r\n\r\n   this.input = await this.optionOrPrompt(&#x5B;           \r\n      {\r\n         type: &quot;input&quot;,\r\n         name: &quot;name&quot;,\r\n         message: &quot;Enter project name&quot;,\r\n         default: this.options.name\r\n      },\r\n      {\r\n         type: &quot;list&quot;,\r\n         name: &quot;endpoint&quot;,\r\n         message: &quot;Endpoint type?&quot;,\r\n            choices: &#x5B;&quot;REST&quot;, &quot;REST\/websocket&quot;, &quot;GraphQL&quot;]\r\n      }\r\n   ]);\r\n}\r\n<\/pre>\n<p>Now we can take this further<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n{\r\n    &quot;name&quot;: &quot;&lt;%= name %&gt;&quot;,\r\n    &quot;version&quot;: &quot;1.0.0&quot;,\r\n    &quot;description&quot;: &quot;&quot;,\r\n    &quot;module&quot;: &quot;es6&quot;,\r\n    &quot;dependencies&quot;: { &lt;% for (let i = 0; i &lt; dependencies.length; i++) {%&gt;\r\n      &quot;&lt;%= dependencies&#x5B;i].name%&gt;&quot;: &quot;&lt;%= dependencies&#x5B;i].version%&gt;&quot;&lt;% if(i &lt; dependencies.length - 1) {%&gt;,&lt;%}-%&gt;\r\n      &lt;%}%&gt;\r\n    },\r\n    &quot;devDependencies&quot;: {\r\n    }\r\n  }\r\n<\/pre>\n<p>and here&#8217;s the changes to the <em>writing<\/em> function<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nwriting() {\r\n\r\n   const dependencies = &#x5B;\r\n      { name: &quot;express&quot;, version: &quot;^4.17.1&quot; },\r\n      { name: &quot;body-parser&quot;, version: &quot;^1.19.0&quot; }\r\n   ]\r\n\r\n   this.fs.copyTpl(\r\n      this.templatePath('package.json'),\r\n      this.destinationPath('public\/package.json'),\r\n      { \r\n         name: this.input.name, \r\n         dependencies: dependencies \r\n      } \r\n   );\r\n}\r\n<\/pre>\n<p>The above is quite convoluted, luckily yeoman includes functionality just for such things, using JSON objects<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst pkgJson = {\r\n   dependencies: {\r\n      &quot;express&quot;: &quot;^4.17.1&quot;,\r\n      &quot;body-parser&quot;: &quot;^1.19.0&quot;\r\n   }\r\n}\r\n\r\nthis.fs.extendJSON(\r\n   this.destinationPath('public\/package.json'), pkgJson)\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>In the previous post we looked at the basics of getting started with yeoman and the tools etc. around it. Let&#8217;s now write some real code. This is going to be a generator for creating a node server using my preferred stack of technologies and allow the person running it to supply data and\/or options [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[284],"tags":[],"class_list":["post-7876","post","type-post","status-publish","format-standard","hentry","category-yeoman"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/7876","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=7876"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/7876\/revisions"}],"predecessor-version":[{"id":8430,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/7876\/revisions\/8430"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=7876"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=7876"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=7876"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}