Skip to content

Commit

Permalink
Fix and update the cli (#847)
Browse files Browse the repository at this point in the history
Fixes: #847
  • Loading branch information
lyzhovnik authored and larixer committed Sep 11, 2018
1 parent 2dca4ee commit b1180f7
Show file tree
Hide file tree
Showing 32 changed files with 443 additions and 287 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@
"webpack-merge": "^4.1.4",
"webpack-node-externals": "^1.7.2",
"webpack-sources": "^1.1.0",
"webpack-virtual-modules": "^0.1.10"
"webpack-virtual-modules": "^0.1.10",
"shelljs": "^0.8.1"
},
"optionalDependencies": {
"dotenv": "^6.0.0",
Expand Down
1 change: 0 additions & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@
"react-stripe-elements": "^1.2.1",
"redux": "^4.0.0",
"serialize-javascript": "^1.4.0",
"shelljs": "^0.8.1",
"shortid": "^2.2.8",
"source-map-support": "^0.5.0",
"sourcemapped-stacktrace": "^1.1.8",
Expand Down
29 changes: 17 additions & 12 deletions tools/cli.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
require('babel-register')({ presets: ['env'], plugins: ['transform-class-properties'] });
require('babel-polyfill');
const prog = require('caporal');
const moduleCmd = require('./cli/module');

const addModuleCommand = require('./cli/commands/addModule');
const deleteModuleCommand = require('./cli/commands/deleteModule');
const CommandInvoker = require('./cli/CommandInvoker');

const commandInvoker = new CommandInvoker(addModuleCommand, deleteModuleCommand);

prog
.version('1.0.0')
.command('addmodule', 'Create a new Module')
.argument('<module>', 'Module name')
.description('Full info: https://github.com/sysgears/apollo-universal-starter-kit/wiki/Apollo-Starter-Kit-CLI')
// Add module
.command('addmodule', 'Create a new Module.')
.argument('<moduleName>', 'Module name')
.argument(
'[location]',
'Where should new module be created. [both, server, client]',
['both', 'server', 'client'],
'both'
)
.action((args, options, logger) => moduleCmd('addmodule', args, options, logger))
.action((args, options, logger) => commandInvoker.runAddModule(args, options, logger))
// Delete module
.command('deletemodule', 'Delete a Module')
.argument('<module>', 'Module name')
.argument(
'[location]',
'Where should new module be created. [both, server, client]',
['both', 'server', 'client'],
'both'
)
.action((args, options, logger) => moduleCmd('deletemodule', args, options, logger));
.argument('<moduleName>', 'Module name')
.argument('[location]', 'Where should we delete module. [both, server, client]', ['both', 'server', 'client'], 'both')
.action((args, options, logger) => commandInvoker.runDeleteModule(args, options, logger));

prog.parse(process.argv);
54 changes: 54 additions & 0 deletions tools/cli/CommandInvoker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const { MODULE_TEMPLATES } = require('./config');

/**
* Class CommandInvoker. Takes all CLI operations and calls certain CLI operation depends of variables.
*/
class CommandInvoker {
/**
* Sets CLI operations (functions).
* @constructor
*
* @param addModule - The function for creating a new module.
* @param deleteModule - The function for deleting existing module.
*/
constructor(addModule, deleteModule) {
this.addModule = addModule;
this.deleteModule = deleteModule;
}

/**
* Calls CLI operation with correct location.
*
* @param func - The func to call.
* @param location - The location for a new module [client|server|both].
* @param args - The function for deleting existing module.
*/
static runCommand(func, location, ...args) {
// client
if (location === 'client' || location === 'both') {
func(...args, 'client');
}
// server
if (location === 'server' || location === 'both') {
func(...args, 'server');
}
}

/**
* Runs operation (function) for creating a new module.
*/
runAddModule(args, options, logger) {
const { moduleName, location = 'both' } = args;
CommandInvoker.runCommand(this.addModule, location, logger, MODULE_TEMPLATES, moduleName);
}

/**
* Runs operation (function) for deleting existing module.
*/
runDeleteModule(args, options, logger) {
const { moduleName, location = 'both' } = args;
CommandInvoker.runCommand(this.deleteModule, location, logger, moduleName);
}
}

module.exports = CommandInvoker;
61 changes: 61 additions & 0 deletions tools/cli/commands/addModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const shell = require('shelljs');
const fs = require('fs');
const chalk = require('chalk');
const { copyFiles, renameFiles, computeModulesPath } = require('../helpers/util');

/**
* Adds module in client or server and adds a new module to the Feature connector.
*
* @param logger - The Logger.
* @param templatesPath - The path to the templates for a new module.
* @param moduleName - The name of a new module.
* @param location - The location for a new module [client|server|both].
* @param finished - The flag about the end of the generating process.
*/
function addModule(logger, templatesPath, moduleName, location, finished = true) {
logger.info(`Copying ${location} files…`);

// create new module directory
const destinationPath = computeModulesPath(location, moduleName);
const newModule = shell.mkdir(destinationPath);

// continue only if directory does not jet exist
if (newModule.code !== 0) {
logger.error(chalk.red(`The ${moduleName} directory is already exists.`));
process.exit();
}
//copy and rename templates in destination directory
copyFiles(destinationPath, templatesPath, location);
renameFiles(destinationPath, moduleName);

logger.info(chalk.green(`✔ The ${location} files have been copied!`));

// get index file path
const modulesPath = computeModulesPath(location);
const indexFullFileName = fs.readdirSync(modulesPath).find(name => name.search(/index/) >= 0);
const indexPath = modulesPath + indexFullFileName;
let indexContent;

try {
// prepend import module
indexContent = `import ${moduleName} from './${moduleName}';\n` + fs.readFileSync(indexPath);
} catch (e) {
logger.error(chalk.red(`Failed to read ${indexPath} file`));
process.exit();
}

// extract Feature modules
const featureRegExp = /Feature\(([^()]+)\)/g;
const [, featureModules] = featureRegExp.exec(indexContent) || ['', ''];

// add module to Feature connector
shell
.ShellString(indexContent.replace(RegExp(featureRegExp, 'g'), `Feature(${moduleName}, ${featureModules})`))
.to(indexPath);

if (finished) {
logger.info(chalk.green(`✔ Module for ${location} successfully created!`));
}
}

module.exports = addModule;
57 changes: 57 additions & 0 deletions tools/cli/commands/deleteModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const shell = require('shelljs');
const fs = require('fs');
const chalk = require('chalk');
const { computeModulesPath } = require('../helpers/util');

/**
* Removes the module from client, server or both locations and removes the module from the Feature connector.
*
* @param logger - The Logger.
* @param moduleName - The name of a new module.
* @param location - The location for a new module [client|server|both].
*/
function deleteModule(logger, moduleName, location) {
logger.info(`Deleting ${location} files…`);
const modulePath = computeModulesPath(location, moduleName);

if (fs.existsSync(modulePath)) {
// remove module directory
shell.rm('-rf', modulePath);

const modulesPath = computeModulesPath(location);

// get index file path
const indexFullFileName = fs.readdirSync(modulesPath).find(name => name.search(/index/) >= 0);
const indexPath = modulesPath + indexFullFileName;
let indexContent;

try {
indexContent = fs.readFileSync(indexPath);
} catch (e) {
logger.error(chalk.red(`Failed to read ${indexPath} file`));
process.exit();
}

// extract Feature modules
const featureRegExp = /Feature\(([^()]+)\)/g;
const [, featureModules] = featureRegExp.exec(indexContent) || ['', ''];
const featureModulesWithoutDeleted = featureModules
.split(',')
.filter(featureModule => featureModule.trim() !== moduleName);

const contentWithoutDeletedModule = indexContent
.toString()
// replace features modules on features without deleted module
.replace(featureRegExp, `Feature(${featureModulesWithoutDeleted.toString().trim()})`)
// remove import module
.replace(RegExp(`import ${moduleName} from './${moduleName}';\n`, 'g'), '');

fs.writeFileSync(indexPath, contentWithoutDeletedModule);

logger.info(chalk.green(`✔ Module for ${location} successfully deleted!`));
} else {
logger.info(chalk.red(`✘ Module ${location} location for ${modulePath} not found!`));
}
}

module.exports = deleteModule;
11 changes: 11 additions & 0 deletions tools/cli/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const path = require('path');

const BASE_PATH = path.resolve(`${__dirname}/../..`);
const TEMPLATES_DIR = `${BASE_PATH}/tools/templates`;
const MODULE_TEMPLATES = `${TEMPLATES_DIR}/module`;

module.exports = {
BASE_PATH,
TEMPLATES_DIR,
MODULE_TEMPLATES
};
63 changes: 63 additions & 0 deletions tools/cli/helpers/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const shell = require('shelljs');
const { pascalize, decamelize } = require('humps');
const { startCase } = require('lodash');
const { BASE_PATH } = require('../config');

/**
* Copies the templates to the destination directory.
*
* @param destinationPath - The destination path for a new module.
* @param templatesPath - The path to the templates for a new module.
* @param location - The location for a new module [client|server|both].
*/
function copyFiles(destinationPath, templatesPath, location) {
shell.cp('-R', `${templatesPath}/${location}/*`, destinationPath);
}

/**
* Renames the templates in the destination directory.
*
* @param destinationPath - The destination path of a new module.
* @param moduleName - The name of a new module.
*/
function renameFiles(destinationPath, moduleName) {
const Module = pascalize(moduleName);

// change to destination directory
shell.cd(destinationPath);

// rename files
shell.ls('-Rl', '.').forEach(entry => {
if (entry.isFile()) {
shell.mv(entry.name, entry.name.replace('Module', Module));
}
});

// replace module names
shell.ls('-Rl', '.').forEach(entry => {
if (entry.isFile()) {
shell.sed('-i', /\$module\$/g, moduleName, entry.name);
shell.sed('-i', /\$_module\$/g, decamelize(moduleName), entry.name);
shell.sed('-i', /\$Module\$/g, Module, entry.name);
shell.sed('-i', /\$MoDuLe\$/g, startCase(moduleName), entry.name);
shell.sed('-i', /\$MODULE\$/g, moduleName.toUpperCase(), entry.name);
}
});
}

/**
* Gets the computed path of the module or modules dir path.
*
* @param location - The location for a new module [client|server|both].
* @param moduleName - The name of a new module.
* @returns {string} - Return the computed path
*/
function computeModulesPath(location, moduleName = '') {
return `${BASE_PATH}/packages/${location}/src/modules/${moduleName}`;
}

module.exports = {
renameFiles,
copyFiles,
computeModulesPath
};
Loading

0 comments on commit b1180f7

Please sign in to comment.