VoxaAI/voxa-cli

View on GitHub
src/VoxaGenerator.ts

Summary

Maintainability
A
0 mins
Test Coverage
/*
 * Copyright (c) 2018 Rain Agency <contact@rain.agency>
 * Author: Rain Agency <contact@rain.agency>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/* tslint:disable:no-submodule-imports no-console */

import fs from "fs-extra";
import Handlebars from "handlebars";
import _ from "lodash";
import path from "path";

interface IAnswers {
  appName: string;
  canFulfill: boolean;
  language: string;
  author: string;
  analytics: string[];
  voxaCli: boolean;
  saveUserInfo: boolean;
  platform: string[];
  accountLinking: boolean;
  newDir: boolean;
}

interface IData {
  appName: string;
  kebabAppName: string;
  folderName: string;
  canFulfill: boolean;
  language: string;
  ext: string;
  author: string;
  ga: boolean;
  dashbot: boolean;
  chatbase: boolean;
  voxaCli: boolean;
  saveUserInfo: boolean;
  usesAlexa: boolean;
  usesGoogleAssistant: boolean;
  usesFacebook: boolean;
  usesTelegram: boolean;
  accountLinking: boolean;
}

export default class VoxaGenerator {
  public data: IData;

  constructor(answers: IAnswers) {
    this.data = {
      appName: answers.appName,
      kebabAppName: _.kebabCase(answers.appName),
      folderName: answers.newDir ? _.kebabCase(answers.appName) : "",
      canFulfill: answers.canFulfill,
      language: answers.language,
      ext: answers.language === "javascript" ? "js" : "ts",
      author: answers.author,
      ga: answers.analytics.includes("ga") || answers.analytics.includes("all"),
      dashbot: answers.analytics.includes("dashbot") || answers.analytics.includes("all"),
      chatbase: answers.analytics.includes("chatbase") || answers.analytics.includes("all"),
      voxaCli: answers.voxaCli,
      saveUserInfo: answers.saveUserInfo,
      usesAlexa: answers.platform.includes("alexa") || answers.platform.includes("all"),
      usesGoogleAssistant: answers.platform.includes("google") || answers.platform.includes("all"),
      usesFacebook: answers.platform.includes("facebook") || answers.platform.includes("all"),
      usesTelegram: answers.platform.includes("telegram") || answers.platform.includes("all"),
      accountLinking: answers.accountLinking
    };
  }

  public async generateProject() {
    try {
      await this.copyPackageFile();
      await this.copyReadmeFile();
      await this.copyGitIgnoreFile();
      if (this.data.voxaCli) {
        await this.copyInteractionFile();
      }
      await this.copyServerless();
      await this.copyServer();
      await this.copySrcFiles();
      if (this.data.accountLinking) {
        await this.copyWebFiles();
      }
      await this.copyAllOtherFiles();
      return Promise.resolve(true);
    } catch (error) {
      console.error(error);
      return Promise.resolve(false);
    }
  }

  private getTemplatePath(...args: any[]): string {
    let fileDir = [__dirname, "..", "..", "templates", this.data.language, ...args];

    if (path.basename(__filename) === "VoxaGenerator.ts") {
      fileDir = [__dirname, "..", "templates", this.data.language, ...args];
    }

    return path.join(...fileDir);
  }

  private async getTemplateFile(...args: any[]): Promise<string> {
    if (path.basename(__filename) === "VoxaGenerator.ts") {
      return fs.readFile(
        path.join(__dirname, "..", "templates", this.data.language, ...args),
        "utf8"
      );
    }

    return fs.readFile(
      path.join(__dirname, "..", "..", "templates", this.data.language, ...args),
      "utf8"
    );
  }

  private async generateHandlebarTemplateFile(...filePath: any[]) {
    const data = this.data;
    const content = await this.getTemplateFile(...filePath);
    const template = Handlebars.compile(content);
    const result = template(data);
    return fs.outputFile(path.join(process.cwd(), this.data.folderName, ...filePath), result);
  }

  private copyFileOrFolder(...filePath: any[]) {
    return fs.copy(
      this.getTemplatePath(...filePath),
      path.join(process.cwd(), this.data.folderName, ...filePath)
    );
  }

  private copyPackageFile() {
    return this.generateHandlebarTemplateFile("package.json");
  }

  private async copyReadmeFile() {
    return this.generateHandlebarTemplateFile("README.md");
  }

  private async copyGitIgnoreFile() {
    await this.copyFileOrFolder("gitignore.txt");
    return fs.rename(
      path.join(process.cwd(), this.data.folderName, "gitignore.txt"),
      path.join(process.cwd(), this.data.folderName, ".gitignore")
    );
  }

  private copyInteractionFile() {
    return this.copyFileOrFolder("interaction.json");
  }

  private copyServerless() {
    return this.generateHandlebarTemplateFile("serverless.yml");
  }

  private copyServer() {
    return this.generateHandlebarTemplateFile(`server.${this.data.ext}`);
  }

  private copySrcIndexFile() {
    return this.generateHandlebarTemplateFile("src", "app", `index.${this.data.ext}`);
  }

  private copySrcModelFile() {
    return this.generateHandlebarTemplateFile("src", "app", `model.${this.data.ext}`);
  }

  private copySrcHandlerFile() {
    return this.generateHandlebarTemplateFile("src", `handler.${this.data.ext}`);
  }

  private copySrcConfigFiles() {
    const configFilePaths = [
      ["src", "config", "local.example.json"],
      ["src", "config", "staging.json"],
      ["src", "config", "production.json"]
    ];

    return Promise.all(
      configFilePaths.map(filePath => this.generateHandlebarTemplateFile(...filePath))
    );
  }

  private async copySrcFiles() {
    const srcFolderPath = this.getTemplatePath("src");
    const destinationPath = path.join(process.cwd(), this.data.folderName, "src");
    await fs.copy(srcFolderPath, destinationPath);

    if (!this.data.saveUserInfo) {
      await fs.remove(
        path.join(process.cwd(), this.data.folderName, "src", "services", `User.${this.data.ext}`)
      );
    }

    const promises = [
      await this.copySrcIndexFile(),
      await this.copySrcModelFile(),
      await this.copySrcHandlerFile(),
      await this.copySrcConfigFiles()
    ];

    return Promise.all(promises);
  }

  private copyWebFiles() {
    return this.copyFileOrFolder("web");
  }

  private async copyAllOtherFiles() {
    const rootFiles = await fs.readdir(this.getTemplatePath());
    const PROCESSED_FILES = [
      "README.md",
      "serverless.yml",
      "package.json",
      "src",
      "interaction.json",
      "gitignore.txt",
      `server.${this.data.ext}`,
      "web"
    ];
    const filteredFiles = rootFiles.filter(file => !PROCESSED_FILES.includes(file));
    return filteredFiles.map(file => {
      fs.copy(this.getTemplatePath(file), path.join(process.cwd(), this.data.folderName, file));
    });
  }
}