@nebulae/cli
Version:
Tools and code generators for microservices developed by Nebula Engineering (http://www.nebulae.com.co)
250 lines (229 loc) • 12.2 kB
JavaScript
const Rx = require('rxjs');
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const git = require('simple-git/promise');
const tmp = require('tmp');
const glob = require("glob");
const replace = require('replace-in-file');
const GitTools = require('../crosscuting/GitTools');
const camelCase = require('camelcase');
const pluralize = require('pluralize');
const replaceLast = require('replace-last');
class MicroserviceGenerator {
constructor() {
}
/**
* Generates a basic microservice structure
* @returns {Rx.Observable}
*/
generateMicroservice$({ frontendId, apiId, projectContext, templateGitUrl, repoGitUrl, entity, gitRepoDirectoryPath }) {
return Rx.Observable.forkJoin(
this.DownlaodTemplate$(templateGitUrl),
this.generateDictionary$(frontendId, apiId, projectContext, repoGitUrl, entity, gitRepoDirectoryPath)
).mergeMap(([repoPath, dictionary]) => Rx.Observable.concat(
Rx.Observable.of(`Template from ${templateGitUrl} was cloned to ${repoPath}`),
Rx.Observable.of(`Dictionary for replacement generated: \n - ${dictionary.map(wc => `${wc.wildcard}->${wc.replacement}`).join('\n - ')}`),
this.initializeGitRepo$(repoPath, repoGitUrl),
this.replaceDirectoriesNames$(repoPath, dictionary),
this.replaceFilesNames$(repoPath, dictionary),
this.replaceFileContents$(repoPath, dictionary),
this.commitAndPushGitChanges$(repoPath),
this.moveToWorkingDirectory$(repoPath, repoGitUrl),
));
}
/**
* Generates replacement words dictionary
* @returns {Rx.Observable}
*/
generateDictionary$(frontendId, apiId, projectContext, repoGitUrl, entity, gitRepoDirectoryPath) {
const repoGitSplitted = repoGitUrl.split('/');
const microserviceName = repoGitSplitted[repoGitSplitted.length - 1].replace('.git', '').replace('ms-', '');
const githubOrg = repoGitSplitted[repoGitSplitted.length - 2];
return Rx.Observable.of([
{ wildcard: 'msshortname', replacement: microserviceName.split('-').map(word => word.substring(0, 4)).join('-') },
{ wildcard: 'msnamecamel', replacement: camelCase(microserviceName) },
{ wildcard: 'msnamepascal', replacement: camelCase(microserviceName, { pascalCase: true }) },
{ wildcard: 'msnameuppercase', replacement: camelCase(microserviceName).toUpperCase() },
{ wildcard: 'msname', replacement: microserviceName },
{ wildcard: 'backendnameuppercase', replacement: microserviceName.toUpperCase() },
{ wildcard: 'backendname', replacement: microserviceName },
{ wildcard: 'msdbname', replacement: microserviceName },
{ wildcard: 'apiidcamellc', replacement: camelCase(apiId).toLowerCase() },
{ wildcard: 'apiid', replacement: apiId },
{ wildcard: 'APIID', replacement: apiId.toUpperCase() },
{ wildcard: 'frontendid', replacement: frontendId },
{ wildcard: 'git_project', replacement: githubOrg },
{ wildcard: 'git_repo_directory_path', replacement: gitRepoDirectoryPath },
{ wildcard: 'project_context_uc', replacement: projectContext.toUpperCase() },
{ wildcard: 'project_context', replacement: projectContext.toLowerCase() },
{ wildcard: 'working_directory', replacement: process.cwd() },
{ wildcard: 'msentitycamel', replacement: camelCase(entity) },
{ wildcard: 'msentitypascal', replacement: camelCase(entity, { pascalCase: true }) },
{ wildcard: 'msentitiespascal', replacement: pluralize(camelCase(entity, { pascalCase: true })) },
{ wildcard: 'msentityname', replacement: entity.toLowerCase() },
/* RUNNABLE TEMPLATE APPROACH */
{ wildcard: 'emi', replacement: frontendId },
{ wildcard: 'micro-service-template-crud-aggregate', replacement: `${microserviceName.toLowerCase().split(' ').join('-')}-${entity.toLowerCase().split(' ').join('-')}-management` },
{ wildcard: 'MicroServiceTemplate', replacement: camelCase(microserviceName, { pascalCase: true }) },
{ wildcard: 'micro-service-template', replacement: microserviceName.toLowerCase().split(' ').join('-') },
{ wildcard: 'MICRO_SERVICE_TEMPLATE', replacement: microserviceName.replace(new RegExp("-", 'g'),"_").toUpperCase().split(' ').join('-') },
{ wildcard: 'micr-serv-temp', replacement: microserviceName.split('-').map(word => word.substring(0, 4)).join('-') },
{ wildcard: 'emi-gateway', replacement: apiId },
{ wildcard: 'emigateway', replacement: camelCase(apiId).toLowerCase() },
{ wildcard: 'crud-aggregate', replacement: entity.toLowerCase().split(' ').join('-') },
{ wildcard: 'crud_aggregate', replacement: entity.toLowerCase().replace(new RegExp("-", 'g'),"_").split(' ').join('_') },
{ wildcard: 'CRUD_AGGREGATE', replacement: entity.toUpperCase().replace(new RegExp("-", 'g'),"_").split(' ').join('_') },
{ wildcard: 'CrudAggregate', replacement: camelCase(entity, { pascalCase: true }) },
{ wildcard: 'crudAggregate', replacement: camelCase(entity, { pascalCase: false }) },
]);
}
/**
* Download the Microservice template, stores it on a temp directory and returns its path
* @param {string} templateGitUrl
* @returns {Rx.Observable}
*/
DownlaodTemplate$(templateGitUrl) {
return Rx.Observable.bindNodeCallback(tmp.dir)()
.map(result => `${result[0]}/repo`)
.mergeMap(tmpPath => GitTools.clone$(templateGitUrl, tmpPath));
}
/**
* config git into microservice repo
* @param {*} repoPath
* @param {*} repoGitUrl
* @returns {Rx.Observable}
*/
initializeGitRepo$(repoPath, repoGitUrl) {
return Rx.Observable.bindNodeCallback(fs.remove)(`${repoPath}/.git`)
.mergeMap(() => Rx.Observable.create(async obs => {
await require('simple-git')(repoPath)
.init()
.add('.')
.commit("first commit!")
.addRemote('origin', repoGitUrl);
obs.next(`Git init at ${repoPath}, added all files, commited and set origin to ${repoGitUrl}`);
obs.complete();
}));
}
/**
* Commits and push git changes
* @param {string} repoPath
* @returns {Rx.Observable}
*/
commitAndPushGitChanges$(repoPath) {
return Rx.Observable.create(async obs => {
await require('simple-git')(repoPath)
.add('.')
.commit("customized template");
//.push('origin', 'master');
obs.next(`Git repo at ${repoPath}, added all changed files and commited changes`);
obs.complete();
})
}
/**
* Replaces directory name wildcard using a dictionary, returns a list of modified directories
* @param {string} repoPath
* @param {string} dictionary
* @returns {Rx.Observable}
*/
replaceDirectoriesNames$(repoPath, dictionary) {
//const dirReplacements = {};
return Rx.Observable.bindNodeCallback(glob)(repoPath + '/**/')
.mergeMap(directories => {
const sortedDirectories = directories.sort((d1, d2) => { return d2.length - d1.length });
return Rx.Observable.from(sortedDirectories);
})
.filter(directory => directory.indexOf('.git/') < 0)
.filter(directory => {
const pathAsArray = directory.split("/");
const dirName = pathAsArray[pathAsArray.length - 2];
const found = dictionary.filter(wildcard => path.basename(dirName).indexOf(wildcard.wildcard) >= 0).length > 0;
return found;
})
.mergeMap(directory => {
const pathAsArray = directory.split("/");
const dirName = pathAsArray[pathAsArray.length - 2];
const wildcard = dictionary.filter(wildcard => path.basename(dirName).indexOf(wildcard.wildcard) >= 0)[0];
const newDirName = replaceLast(directory, wildcard.wildcard, wildcard.replacement);
console.log(`***** newDirName: ${directory} -> ${newDirName}`);
return Rx.Observable.bindNodeCallback(fs.rename)(directory, newDirName)
.mapTo(`${directory}->${newDirName}`)
}).toArray()
.map(files => `Renamed directories:\n - ${files.join('\n - ')}`);
}
/**
* Replaces filename wildcard using a dictionary, returns a list of modified files
* @param {string} repoPath
* @param {string} dictionary
* @returns {Rx.Observable}
*/
replaceFilesNames$(repoPath, dictionary) {
return Rx.Observable.bindNodeCallback(glob)(repoPath + '/**/*', { dot: true })
.mergeMap(files => Rx.Observable.from(files))
.filter(file => file.indexOf('.git/') < 0)
.filter(file => file.indexOf('.history/') < 0)
.filter(file => fs.lstatSync(file).isFile())
.map(file => {
const wildcards = dictionary.filter(wildcard => path.basename(file).indexOf(wildcard.wildcard) >= 0);
if (wildcards.length <= 0) {
return undefined;
} else {
const wildcard = wildcards[0];
return {
file,
wildcard: wildcard.wildcard,
replacement: wildcard.replacement
};
}
}).filter(filesToReplace => filesToReplace !== undefined)
.mergeMap(filesToReplace => {
const newFileName = filesToReplace.file.replace(filesToReplace.wildcard, filesToReplace.replacement);
return Rx.Observable.bindNodeCallback(fs.rename)(filesToReplace.file, newFileName)
.mapTo(`${filesToReplace.file}->${newFileName}`)
}).toArray()
.map(files => `Renamed files:\n - ${files.join('\n- ')}`);
}
/**
* Replaces file contents wildcard using a dictionary, returns a list of modified files
* @param {string} repoPath
* @param {string} dictionary
* @returns {Rx.Observable}
*/
replaceFileContents$(repoPath, dictionary) {
return Rx.Observable.bindNodeCallback(glob)(repoPath + '/**/*', { dot: true })
.mergeMap(files => Rx.Observable.from(files))
.filter(file => file.indexOf('.git/') < 0)
.filter(file => file.indexOf('.history/') < 0)
.filter(file => fs.lstatSync(file).isFile())
.toArray()
.mergeMap(files => {
const options = {
files,
from: [],
to: [],
};
dictionary.forEach(wildcard => {
options.from.push(new RegExp(wildcard.wildcard, 'g'));
options.to.push(wildcard.replacement);
});
return Rx.Observable.bindNodeCallback(replace)(options)
.map(files => `Modified files contents:\n - ${files.join('\n - ')}`);
});
}
/**
* moves the microservice repo from its temp directory to the working directory
* @param {string} repoPath
* @returns {Rx.Observable}
*/
moveToWorkingDirectory$(repoPath, repoGitUrl) {
const repoGitSplitted = repoGitUrl.split('/');
const microserviceName = repoGitSplitted[repoGitSplitted.length - 1].replace('.git', '');
const srcpath = repoPath;
const dstpath = `${process.cwd()}/${microserviceName}`;
return Rx.Observable.defer(() => fs.move(srcpath, dstpath))
.mapTo(`Moved repo from ${srcpath} to ${dstpath}`);
}
}
module.exports = MicroserviceGenerator;