webgme/webgme-cli

View on GitHub
src/BaseManager.js

Summary

Maintainability
A
3 hrs
Test Coverage
// jshint node: true
"use strict";

var _ = require("lodash"),
  path = require("path"),
  fs = require("fs"),
  rm_rf = require("rimraf"),
  exists = require("exists-file"),
  R = require("ramda"),
  mkdir = require("mkdirp"),
  Logger = require("./Logger"),
  utils = require("./utils"),
  PROJECT_CONFIG = "webgme-setup.json";
const { spawn } = require("child_process");

var BaseManager = function (logger) {
  this._logger = logger || new Logger();
};

BaseManager.prototype.start = async function (options, callback) {
  var root = utils.getRootPath();

  if (!root) {
    var err = `No webgme app found!`;
    this._logger.error(err);
    return callback(err);
  }

  if (options.hard) {
    this._logger.write("Removing installed modules");
    rm_rf(
      path.join(root, "node_modules"),
      this._start.bind(this, root, callback)
    );
  } else {
    try {
      await this._start(root);
      callback();
    } catch (err) {
      callback(err);
      throw err;
    }
  }
};

BaseManager.prototype._start = async function (root) {
  this._logger.write("Installing dependencies...");
  await this.spawn("npm", ["install"]);

  const webgmePath = path.join(root, "node_modules", "webgme-engine");
  if (!exists(webgmePath)) {
    this._logger.write("Installing webgme...");
    await this.spawn("npm", ["install", "webgme"]);
  }

  await this.spawn("npm", ["start"]);
};

BaseManager.prototype.spawn = async function (cmd, args) {
  const job = spawn(cmd, args);
  job.stdout.pipe(process.stdout);
  job.stderr.pipe(process.stderr);
  await new Promise((resolve, reject) =>
    job.on("close", (code) => (code ? reject(code) : resolve()))
  );
};

BaseManager.prototype.init = function (args, callback) {
  // Create new project
  var project, name, err;

  project = path.resolve(args.name || process.cwd());
  name = path.basename(project).toLowerCase();
  this._logger.info("Creating new project at " + project);

  if (!args.name) {
    // Creating in current directory
    // Check if the project contains webgme-setup.json file
    var setupJsonPath = path.join(process.cwd(), PROJECT_CONFIG);
    if (exists(setupJsonPath)) {
      err = "Cannot create project here. Project already exists.";
      this._logger.error(err);
      return callback(err);
    }
  } else {
    // Check that the target directory doesn't exist
    if (exists(project)) {
      err = "Cannot create " + args.name + ". File exists.";
      this._logger.error(err);
      return callback(err);
    }
  }

  mkdir(
    project,
    function (err) {
      if (err) {
        this._logger.error(`Creating project failed: ${err}`);
        return rm_rf(project, (e) => {
          if (e) {
            this._logger.error(`Cleanup failed: ${e}`);
          }
          return callback(err);
        });
      }

      // Create the package.json
      this._createPkgJson(project, name);

      // Create the base directories
      BaseManager._createBasicFileStructure(project);

      // Create the webgme files
      this._createWebGMEFiles(project);

      // Create the project info file
      var webgmeInfo = {
        components: {},
        dependencies: {},
      };
      fs.writeFileSync(
        path.join(project, PROJECT_CONFIG),
        JSON.stringify(webgmeInfo, null, 2)
      );
      utils.updateWebGMEConfig(project);

      this._logger.write(
        "Created project at " +
          project +
          ".\n\nStart your WebGME " +
          "app with 'webgme start' from the project root.\n" +
          "It is recommended to run 'npm init' from the within project " +
          "to finish configuration."
      );
      callback();
    }.bind(this)
  );
};

BaseManager.prototype._createPkgJson = function (project, name) {
  var pkgJsonTemplatePath = path.join(
      __dirname,
      "res",
      "package.template.json"
    ),
    pkgJsonTemplate = _.template(fs.readFileSync(pkgJsonTemplatePath)),
    toolJsonPath = path.join(__dirname, "..", "package.json"),
    toolJson = require(toolJsonPath),
    pkgContent = {
      webgmeVersion: toolJson.dependencies["webgme-engine"],
      name: name,
    },
    outputPkgJson = path.join(project, "package.json"),
    originalPkgJson = {},
    newPkgJson = JSON.parse(pkgJsonTemplate(pkgContent)),
    pkgJson;

  // Load existing package.json, if exists
  if (exists(outputPkgJson)) {
    originalPkgJson = require(outputPkgJson);
    // We will remove the require cache for the package.json since
    // we are changing it now
    delete require.cache[require.resolve(outputPkgJson)];
  }
  pkgJson = _.extend({}, originalPkgJson, newPkgJson);

  // Copy the dev and regular dependencies separately (_.extend is shallow)
  pkgJson.dependencies = _.extend(
    originalPkgJson.dependencies || {},
    newPkgJson.dependencies
  );
  pkgJson.devDependencies = _.extend(
    originalPkgJson.devDependencies || {},
    newPkgJson.devDependencies
  );

  this._logger.info("Writing package.json to " + outputPkgJson);

  fs.writeFileSync(outputPkgJson, JSON.stringify(pkgJson, null, 2));
};

BaseManager._createBasicFileStructure = function (project) {
  var vizDir = path.join("src", "visualizers"),
    dirs = ["src", "test", path.join("src", "common"), vizDir];

  dirs.forEach((dir) => {
    var absDir = path.join(project, dir);
    fs.mkdirSync(absDir);
  });

  fs.writeFileSync(path.join(project, vizDir, "Visualizers.json"), "[]");
};

BaseManager.prototype._createWebGMEFiles = function (project) {
  // Create webgme config info
  var configDir = path.join(project, "config");

  // Config files
  fs.mkdirSync(configDir);
  fs.readdirSync(path.join(__dirname, "res", "config"))
    .map(R.pipe(R.nthArg(0), path.join.bind(path, "config"))) // Add 'config/' for each
    .forEach(BaseManager._copyFileToProject.bind(null, project, ""));

  // Create app.js
  BaseManager._copyFileToProject(project, "", "app.js");

  // Create test fixtures
  BaseManager._copyFileToProject(project, "test", "globals.js");

  // Create .gitignore
  BaseManager._createGitIgnore(project);

  // Create src/common README.md
  BaseManager._createCommonReadme(project);

  // Create README.md
  BaseManager._createReadme(project);
};

BaseManager._createReadme = function (project) {
  var boilerplatePath = path.join(__dirname, "res", "README.md.ejs"),
    readme = _.template(fs.readFileSync(boilerplatePath, "utf8")),
    dstPath = path.join(project, "README.md"),
    content = {
      name: path.basename(project),
    };

  if (!exists(dstPath)) {
    fs.writeFileSync(dstPath, readme(content));
  }
};

BaseManager._createCommonReadme = function (project) {
  var boilerplatePath = path.join(__dirname, "res", "README.common.md.ejs"),
    readme = _.template(fs.readFileSync(boilerplatePath, "utf8")),
    dstPath = path.join(project, "src", "common", "README.md"),
    content = {
      name: path.basename(project),
    };

  if (!exists(dstPath)) {
    fs.writeFileSync(dstPath, readme(content));
  }
};

/**
 * Copy the file to the project if the file doesn't already exist
 *
 * @param {String} project
 * @param {String} subPath
 * @param {String} filename
 * @return {undefined}
 */
BaseManager._createGitIgnore = function (project) {
  var boilerplatePath = path.join(__dirname, "res", "gitignore"),
    dstPath = path.join(project, ".gitignore");

  if (!exists(dstPath)) {
    fs.createReadStream(boilerplatePath).pipe(fs.createWriteStream(dstPath));
  }
};

BaseManager._copyFileToProject = function (project, subPath, filename) {
  var boilerplatePath, dstPath;

  subPath = subPath || "";
  boilerplatePath = path.join(__dirname, "res", filename);
  dstPath = path.join(project, subPath, filename);

  fs.createReadStream(boilerplatePath).pipe(fs.createWriteStream(dstPath));
};

module.exports = BaseManager;