<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Duwaine</title>
    <description>The latest articles on DEV Community by Duwaine (@duwainevandriel).</description>
    <link>https://dev.to/duwainevandriel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F380530%2F3d254fc6-784e-4c42-a642-479fb2a9a65e.png</url>
      <title>DEV Community: Duwaine</title>
      <link>https://dev.to/duwainevandriel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/duwainevandriel"/>
    <language>en</language>
    <item>
      <title>Build your own Project Template Generator</title>
      <dc:creator>Duwaine</dc:creator>
      <pubDate>Mon, 11 May 2020 13:19:04 +0000</pubDate>
      <link>https://dev.to/duwainevandriel/build-your-own-project-template-generator-59k4</link>
      <guid>https://dev.to/duwainevandriel/build-your-own-project-template-generator-59k4</guid>
      <description>&lt;p&gt;Build your own custom cli project template generator from anywhere on your machine. I have used the guidelines from this article &lt;a href="https://medium.com/@pongsatt/how-to-build-your-own-project-templates-using-node-cli-c976d3109129" rel="noopener noreferrer"&gt;How to build your own project templates using Node CLI and typescript&lt;/a&gt; but have run into a few problems so I decided to sort them out myself and create a new article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do I want to create my own template generator?
&lt;/h3&gt;

&lt;p&gt;From time to time you want to create a new project but it is based on something you have set up before. Let's say I need React with Node but I forget how I implemented everything in previous projects so instead of sourcing through old projects why not create a template for that and use it from this template generator?&lt;/p&gt;

&lt;p&gt;If you just want to skip ahead and not build your own here is a repo to the  &lt;a href="https://github.com/DuwainevanDriel/project-template-generator.git" rel="noopener noreferrer"&gt;Project Template Generator&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lets get started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create new typescript project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. create a new project folder (mkdir PROJECT_NAME &amp;amp; cd PROJECT NAME)
2. run npm init (to initialize a new node project)
3. run npm add -D typescript ts-node nodemon
- ts-node is used to run typescript without compiling
- nodemon is used to run/restart node automatically when files changed
4. run npx tsc --init
5. adjust tsconfig.json to the following
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "declaration": true,
    "sourceMap": true,
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
  }
}
6. create folder src and index.ts file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add sample-project to your main template generator project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. create a the following folder tree inside your project: src/templates/sample-project.
2. inside the sample-projects folder create a package.json file that contains the following:
{
  "name": "sample-project",
  "version": "1.0.0",
  "license": "MIT"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your structure now should look something like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmwmlcdnhk1mi4rhzenqd.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmwmlcdnhk1mi4rhzenqd.PNG" alt="Starter Setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Add the following at the top of "src/index.ts"
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. #!/usr/bin/env node
   This is known as "shebang" this tells node to run the typescript code.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Now lets read the templates folder as choices in the cli
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. run npm add -D @types/node @types/inquirer
2. run npm add inquirer
3. update src/index.ts

   import * as fs from 'fs';
   import * as path from 'path';
   import * as inquirer from 'inquirer';
   import chalk from 'chalk';

   const CHOICES = fs.readdirSync(path.join(__dirname, 'templates'));
   const QUESTIONS = [
   {
      name: 'template',
      type: 'list',
      message: 'What project template would you like to use?',
      choices: CHOICES
   },
   {
      name: 'name',
      type: 'input',
      message: 'New project name?'
   }];

   inquirer.prompt(QUESTIONS)
   .then(answers =&amp;gt; {
      console.log(answers);
   });

4. to test update package.json script
   "scripts": {
      "start": "ts-node src/index.ts"
   }
   and run npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fggdi57xuiaum8p94jos7.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fggdi57xuiaum8p94jos7.PNG" alt="npm start outcome"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There will be more templates in the list when you add your own in src/templates&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Use the input options
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. update src/index.ts with the following 
2. export interface CliOptions {
    projectName: string
    templateName: string
    templatePath: string
    tartgetPath: string
   }
   const CURR_DIR = process.cwd();
   inquirer.prompt(QUESTIONS)
     .then(answers =&amp;gt; {
      const projectChoice = answers['template'];
      const projectName = answers['name'];
      const templatePath = path.join(__dirname, 'templates', projectChoice);
      const tartgetPath = path.join(CURR_DIR, projectName);
      const options: CliOptions = {
          projectName,
          templateName: projectChoice,
          templatePath,
          tartgetPath
      }
      console.log(options);
   });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Create your project folder
&lt;/h3&gt;

&lt;p&gt;At the question New project name? create a new folder "projectName" in the root directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function createProject(projectPath: string) {
    if (fs.existsSync(projectPath)) {
        console.log(chalk.red(`Folder ${projectPath} exists. Delete or use another name.`));
        return false;
    }
    fs.mkdirSync(projectPath);

    return true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case of failure stop the function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inquirer.prompt(QUESTIONS)
.then(answers =&amp;gt; {
    ....

    if (!createProject(tartgetPath)) {
        return;
    }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Copy files and folders from chosen template to new project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Add to src/index.ts
2. // list of file/folder that should not be copied
   const SKIP_FILES = ['node_modules', '.template.json'];
   function createDirectoryContents(templatePath: string, projectName: string) {
    // read all files/folders (1 level) from template folder
    const filesToCreate = fs.readdirSync(templatePath);
    // loop each file/folder
    filesToCreate.forEach(file =&amp;gt; {
        const origFilePath = path.join(templatePath, file);

        // get stats about the current file
        const stats = fs.statSync(origFilePath);

        // skip files that should not be copied
        if (SKIP_FILES.indexOf(file) &amp;gt; -1) return;

        if (stats.isFile()) {
            // read file content and transform it using template engine
            let contents = fs.readFileSync(origFilePath, 'utf8');
            // write file to destination folder
            const writePath = path.join(CURR_DIR, projectName, file);
            fs.writeFileSync(writePath, contents, 'utf8');
        } else if (stats.isDirectory()) {
            // create folder in destination folder
            fs.mkdirSync(path.join(CURR_DIR, projectName, file));
            // copy files/folder inside current folder recursively
            createDirectoryContents(path.join(templatePath, file), 
            path.join(projectName, file));
        }
    });
   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following code after you create the tempalte&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;....
if (!createProject(tartgetPath)) {
    return;
}
createDirectoryContents(templatePath, projectName);
....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. Testing the program as a CLI
&lt;/h3&gt;

&lt;p&gt;Install tool "shx" for building script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Run npm add -D shx
2. Add the following build script to package.json 
   "build": "tsc &amp;amp;&amp;amp; shx rm -rf dist/templates &amp;amp;&amp;amp; shx cp -r src/templates dist"
3. npm run build
4. Add bin to package.json
   "bin": {
      "template-generator": "./dist/index.js"
   }
5. Register "template-generator" as a command line interface
   run npm link
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If successful, you can run the command "template-generator" anywhere on your machine. (Make sure you have permission to read/write files)&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Final step: Rename project as new project created by input
&lt;/h3&gt;

&lt;p&gt;So now you can choose a template from the given questions list and then input a new project name but the template files that are being copied over are exactly the same like the project name in the new package.json and we want to automate that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. update template "src/templates/sample-project/package.json" with a placholder name
   {
      "name": "&amp;lt;%= projectName %&amp;gt;",
      "version": "1.0.0",
      ....
   }
2. npm add ejs
   add -D @types/ejs
3. update src/utils/template.ts to render template under utils
   import * as ejs from 'ejs';
   export interface TemplateData {
      projectName: string
   }
   export function render(content: string, data: TemplateData) {
      return ejs.render(content, data);
   }
4. Add code to transform the content inside "src/index.ts" function "createDirectoryContents"
   if (stats.isFile()) {
      // read file content and transform it using template engine
      let contents = fs.readFileSync(origFilePath, 'utf8');
      contents = template.render(contents, { projectName });
   }
5. run npm build and then generate-template to test that the new project name is inserted in the "&amp;lt;%= projectName %&amp;gt;" placeholder.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your project template generator should now be complete.&lt;/p&gt;

&lt;p&gt;Here is the full src/index.ts file incase you have missed something&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env node

import * as fs from 'fs';
import * as path from 'path';
import * as inquirer from 'inquirer';
import chalk from 'chalk';
import * as template from './utils/template';
import * as shell from 'shelljs';

const CHOICES = fs.readdirSync(path.join(__dirname, 'templates'));
const QUESTIONS = [
{
    name: 'template',
    type: 'list',
    message: 'What template would you like to use?',
    choices: CHOICES
},
{
    name: 'name',
    type: 'input',
    message: 'Please input a new project name:'
}];

export interface CliOptions {
    projectName: string
    templateName: string
    templatePath: string
    tartgetPath: string
}

const CURR_DIR = process.cwd();

inquirer.prompt(QUESTIONS).then(answers =&amp;gt; {
    const projectChoice = answers['template'];
    const projectName = answers['name'];
    //@ts-ignore
    const templatePath = path.join(__dirname, 'templates', projectChoice);
    //@ts-ignore
    const tartgetPath = path.join(CURR_DIR, projectName);

    const options: CliOptions = {
        //@ts-ignore
        projectName,
        //@ts-ignore
        templateName: projectChoice,
        templatePath,
        tartgetPath
    }

    if (!createProject(tartgetPath)) {
        return;
    }

    //@ts-ignore
    createDirectoryContents(templatePath, projectName);

    postProcess(options);
});

function createProject(projectPath: string) {
    if (fs.existsSync(projectPath)) {
        console.log(chalk.red(`Folder ${projectPath} exists. Delete or use another name.`));
        return false;
    }
    fs.mkdirSync(projectPath);

    return true;
}

const SKIP_FILES = ['node_modules', '.template.json'];

function createDirectoryContents(templatePath: string, projectName: string) {
    // read all files/folders (1 level) from template folder
    const filesToCreate = fs.readdirSync(templatePath);
    // loop each file/folder
    filesToCreate.forEach(file =&amp;gt; {
        const origFilePath = path.join(templatePath, file);

        // get stats about the current file
        const stats = fs.statSync(origFilePath);

        // skip files that should not be copied
        if (SKIP_FILES.indexOf(file) &amp;gt; -1) return;

        if (stats.isFile()) {
            // read file content and transform it using template engine
            let contents = fs.readFileSync(origFilePath, 'utf8');
            contents = template.render(contents, { projectName });
            // write file to destination folder
            const writePath = path.join(CURR_DIR, projectName, file);
            fs.writeFileSync(writePath, contents, 'utf8');
        } else if (stats.isDirectory()) {
            // create folder in destination folder
            fs.mkdirSync(path.join(CURR_DIR, projectName, file));
            // copy files/folder inside current folder recursively
            createDirectoryContents(path.join(templatePath, file), path.join(projectName, file));
        }
    });
}

function postProcess(options: CliOptions) {
    const isNode = fs.existsSync(path.join(options.templatePath, 'package.json'));
    if (isNode) {
        shell.cd(options.tartgetPath);
        const result = shell.exec('npm install');
        if (result.code !== 0) {
            return false;
        }
    }

    return true;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a link to the full project to use : &lt;a href="https://github.com/DuwainevanDriel/project-template-generator.git" rel="noopener noreferrer"&gt;Project Template Generator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>typescript</category>
      <category>npm</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
