jmms
Version:
Jmms cli tools, Jmms is a java meta-micro-service framework
361 lines (306 loc) • 11.1 kB
JavaScript
const _ = require('lodash');
const chalk = require('chalk');
const metas = require('./metas');
const log = require('./log');
const utils = require('./utils');
const fs = require('fs');
const path = require('path');
const proc = require('child_process');
const pkg = require('../package.json');
const app = require('./app');
const table = require('text-table');
const cheerio = require('cheerio');
const iconv = require('iconv-lite');
const os = require('os');
const setupDefaultArgumentsAndOptions = (generator, opts) => {
//setup meta
var meta = opts.meta;
if(!meta) {
meta = utils.resolveMeta(module.parent);
}
generator.meta = meta;
//setup arguments.
if(meta.arg) {
const args = opts.args ? opts.args : [];
const arg = meta.arg;
if(arg.required && args.length == 0) {
log.error(`Missing argument ${chalk.yellow(arg.name)} ${chalk.white('- ' + arg.desc + "")}`);
process.exit(0);
}
if(args.length > 0) {
opts[arg.name] = args[0];
}else if(arg.defautls) {
opts[arg.name] = arg.default;
}
}
//setup options
if(meta.opts) {
Object.keys(meta.opts).forEach((name) => {
generator.option(name, meta.opts[name]);
});
}
//delete unused properties from options.
delete opts.meta;
delete opts.args;
}
var Generator = require('yeoman-generator');
module.exports = class extends Generator {
constructor(args, opts, checkProjectDirectory) {
super(args, opts, pkg);
if(opts.help) {
this._printHelpAndExit(opts.meta);
}
//setup default argument & options
setupDefaultArgumentsAndOptions(this, opts);
//check and setup app.
const appDirectory = path.join(this.destinationRoot(), 'app');
const configFile = utils.existsJsonOrYaml(path.join(appDirectory, 'config'));
const isProjectDir = null != configFile;
if(!isProjectDir && !(checkProjectDirectory === false)) {
log.warn(`'${this.destinationRoot()}' is not a jmms project directory, exit!`);
process.exit(0);
}
if(isProjectDir) {
//setup app
this.app = app.init(appDirectory, this.options);
this.api = this.app.api;
//resolve parent pom
const xml = fs.readFileSync(this.destinationPath('./pom.xml'));
let $ = cheerio.load(xml, { xmlMode: true });
this.parentRelativePath = $("project > parent > relativePath").text();
if(this.parentRelativePath.length <= 0) {
this.parentRelativePath = undefined;
}
}
}
_printHelpAndExit(meta) {
const optionsHelp = this._optionsHelp(meta);
if(optionsHelp) {
console.log(`${optionsHelp}`);
}
const argumentsHelp = this._argumentsHelp(meta);
if(argumentsHelp) {
console.log(`\n\n${argumentsHelp}`);
}
const usage = this._usageHelp();
if(usage.length > 0) {
console.log(`\n\n${usage}\n`);
}else{
console.log('\n');
}
process.exit(0);
}
_argumentsHelp(meta) {
if(!meta.arg) {
return undefined;
}
const arg = meta.arg;
const rows = [];
rows.push([
'',
'',
arg.required ? '<' + arg.name + '>' : '[' + arg.name + ']',
arg.type ? arg.type.name : '_',
'defaults:' + (arg.defaults ? arg.defaults : 'none'),
'-',
arg.desc
])
return " Arguments:\n\n" + table(rows);
}
_optionsHelp(meta) {
if(!meta.opts) {
return undefined;
}
const opts = meta.opts;
const rows = [];
Object.keys(opts).forEach((name) => {
const opt = opts[name];
rows.push([
'',
'',
(opt.alias ? '-' + opt.alias : '') + ',',
'--' + name,
opt.desc
])
});
return "\n ---\n\n" + table(rows);
}
_usageHelp() {
const filepath = path.resolve(this.sourceRoot(), '../USAGE');
const exists = fs.existsSync(filepath);
if(exists) {
return fs.readFileSync(filepath, 'utf8');
}else {
return '';
}
}
_findExecutableJar() {
const targetDir = path.join(this.destinationRoot(), 'target');
if(fs.existsSync(targetDir)) {
const files = fs.readdirSync(targetDir);
for (var i = 0, len = files.length; i < len; i++) {
const filename = files[i];
if(filename.endsWith('-sources.jar')) {
continue;
}
if(filename.endsWith('-javadoc.jar')) {
continue;
}
if(filename.endsWith('-docker-info.jar')) {
continue;
}
if(filename.endsWith('.jar')) {
const fullPath = path.join(targetDir, filename);
return fullPath;
}
}
}
return undefined;
}
_findOrCreateJar(done) {
done.call(this,this._findOrCreateJarOnly());
}
_findOrCreateJarOnly() {
var jar = this._findExecutableJar();
if(!jar) {
if(this.parentRelativePath) {
log.info("Packaging executable jar use parent '" + this.parentRelativePath + "' ....\n");
this.spawnCommandSync('mvn',['package', '-Dmaven.javadoc.skip=true', '-Dmaven.test.skip=true', '-f', this.parentRelativePath]);
}else {
log.info("Packaging executable jar....\n");
this.spawnCommandSync('mvn',['package', '-Dmaven.javadoc.skip=true', '-Dmaven.test.skip=true']);
}
//find again.
jar = this._findExecutableJar();
}
return jar;
}
_runJavaCli(callback) {
var mvnArgs = '';
if(log.isDebugEnabled()) {
mvnArgs = '-X';
}
var execArgs = '';
const profile = this.options['profile'];
if(profile) {
execArgs = '-Dapp.profile=' + profile;
}
//const jar = this._findOrCreateJarOnly();
//mvn exec:java -Dexec.mainClass=jmms.devtools.CliMain
const cmd = 'mvn';
const args = _.compact([mvnArgs, 'exec:java', '-Dexec.mainClass=jmms.devtools.CliMain', '-Dexec.daemonThreadJoinTimeout=1', execArgs]);
log.exec(cmd, args);
const crossSpawn = require('cross-spawn');
const spawn = crossSpawn(cmd, args);
/*
const spawn = proc.spawn('java',['-classpath', jar,
'-Djmms.dir=' + path.join(this.destinationRoot(), 'app'),
'-Dloader.main=jmms.devtools.CliMain',
'org.springframework.boot.loader.PropertiesLauncher']);
*/
var init;
var buf = '';
var result;
var done;
var closed = false;
const cli = {
exec: function(command, params, callback) {
done = callback;
log.debug(`Send cli command '${command}' ...`);
spawn.stdin.write(JSON.stringify({command:command,params:params}) + '\n');
},
exit: function() {
closed = true;
spawn.stdin.write('exit');
spawn.stdin.end();
}
};
// todo: how to get the default charset of system?
var charset = 'utf8';
if(process.platform == 'win32'){
charset = 'gbk';
}
spawn.stderr.pipe(iconv.decodeStream(charset)).pipe(process.stderr);
spawn.stdout.on('data', (out) => {
var s = iconv.decode(out, charset);
//wait until the buf contains one or more lines.
buf = buf + s;
if(buf.indexOf('\n') < 0) {
return;
}
if(!init && buf.indexOf('Waiting command') >= 0) {
init = true;
log.debug('Waiting command from callback...');
callback.call(this, cli);
}
//split lines
var lastLineFully = buf.endsWith('\n');
var lines = _.split(buf, '\n');
for(var i=0;i<lines.length;i++) {
var line = lines[i];
const start = line.indexOf('**start**');
const end = line.indexOf('**end**');
if(start >= 0 && end > 0) {
result = line.substring(start + 9, end);
}else {
if(i < lines.length - 1 || lastLineFully) {
if(log.isDebugEnabled()) {
console.log(line);
}
}
}
}
//reset the buf
if(!lastLineFully) {
buf = lines[lines.length-1];
}else {
buf = '';
}
if(result) {
const response = JSON.parse(result);
buf = undefined;
result = undefined;
const err = response.status >= 300;
if(err) {
if(response.status >= 500) {
log.info(`\n${chalk.red.bold('ERROR!')} \n\n${chalk.red(response.result)}\n`);
}else {
log.info(`\n${chalk.yellow('Error :' + response.result)}\n`);
}
}
const exit = done?done.call(this, err, response.result):undefined;
if(!closed && (exit == undefined || exit === true)) {
spawn.stdin.write('exit');
spawn.stdin.end();
}
}
});
spawn.stdin.setDefaultEncoding("utf8");
spawn.on('close', (code) => {
closed = true;
//log.info('Java cli exit ' + code);
});
}
_execCliCommand(command, param,callback) {
this._runJavaCli((cli) => {
cli.exec(command, param,(err, r) => {
cli.exit();
callback.call(this, err, r);
});
});
}
_isMavenProject() {
const pom = this.destinationPath('./pom.xml');
return fs.existsSync(pom);
}
_execSync(cmd, args) {
if(_.isArray(args)) {
args = _.pull(args, '');
}
log.exec(cmd, args);
return this.spawnCommandSync(cmd, args);
}
_jmms() {
return require(this.destinationPath('.jmms.json'));
}
}