Skip to content

Commit

Permalink
feat: command register mechanism.
Browse files Browse the repository at this point in the history
  • Loading branch information
lewischeng committed Aug 17, 2017
1 parent 8573659 commit febe1c3
Show file tree
Hide file tree
Showing 21 changed files with 739 additions and 7 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
root = true

[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true

[{lib,src,test}/**.js]
indent_style = space
indent_size = 2
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text eol=lf
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.idea/
.DS_Store
node_modules
npm-debug.log
npm-debug.log
debug.log
6 changes: 6 additions & 0 deletions bin/feflow
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node

'use strict';

require('../lib/feflow')();

65 changes: 65 additions & 0 deletions lib/core/command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use strict';

const Promise = require('bluebird');
const abbrev = require('abbrev');

class Command {

constructor() {
this.store = {};
this.alias = {};
}

get(name) {
name = name.toLowerCase();
return this.store[this.alias[name]];
}

list() {
return this.store;
}

register(name, desc, options, fn) {
if (!name) throw new TypeError('name is required');

if (!fn) {
if (options) {
if (typeof options === 'function') {
fn = options;

if (typeof desc === 'object') { // name, options, fn
options = desc;
desc = '';
} else { // name, desc, fn
options = {};
}
} else {
throw new TypeError('fn must be a function');
}
} else {
// name, fn
if (typeof desc === 'function') {
fn = desc;
options = {};
desc = '';
} else {
throw new TypeError('fn must be a function');
}
}
}

if (fn.length > 1) {
fn = Promise.promisify(fn);
} else {
fn = Promise.method(fn);
}

const c = this.store[name.toLowerCase()] = fn;
c.options = options;
c.desc = desc;

this.alias = abbrev(Object.keys(this.store));
}
}

module.exports = Command;
128 changes: 128 additions & 0 deletions lib/core/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
'use strict';

const vm = require('vm');
const pathFn = require('path');
const Module = require('module');
const _ = require('lodash');
const osenv = require('osenv');
const Promise = require('bluebird');
const chalk = require('chalk');
const fs = require('hexo-fs');
const logger = require('../utils/logger');
const Command = require('./command');
const pkg = require('../../package.json');
const sep = pathFn.sep;

class Feflow {

/**
* Set root and plugin path, context variable include log, cli command object.
* @param args
*/
constructor(args) {
args = args || {};

const base = pathFn.join(osenv.home(), './.feflow');

this.version = pkg.version;
this.baseDir = base + sep;
this.pkgPath = pathFn.join(base, 'package.json');
this.pluginDir = pathFn.join(base, 'node_modules') + sep;

this.log = logger({
debug: Boolean(args.debug)
});

this.cmd = new Command();
}

/**
* Read config and load internal and external plugins.
*/
init() {
const self = this;

this.log.debug('Feflow version: %s', chalk.magenta(this.version));

// Load internal plugins
require('../plugins/console')(this);
require('../plugins/generator')(this);
require('../plugins/install')(this);

// Load external plugins
return Promise.each([
'load_plugins'
], function(name) {
return require('./' + name)(self);
}).then(function() {
// Init success
self.log.debug('init success!');
});

}

/**
* Call a command in console.
* @param name
* @param args
* @param callback
*/
call(name, args, callback) {
if (!callback && typeof args === 'function') {
callback = args;
args = {};
}

const self = this;

return new Promise(function(resolve, reject) {
const c = self.cmd.get(name);

if (c) {
c.call(self, args).then(resolve, reject);
} else {
reject(new Error('Command `' + name + '` has not been registered yet!'));
}
}).asCallback(callback);
}

/**
* Load a plugin with vm module and inject feflow variable,
* feflow is an instance and has context environment.
*
* @param path {String} Plugin path
* @param callback {Function} Callback
*/
loadPlugin(path, callback) {
const self = this;

return fs.readFile(path).then((script) => {

const module = new Module(path);
module.filename = path;
module.paths = Module._nodeModulePaths(path);

function require(path) {
return module.require(path);
}

require.resolve = function(request) {
return Module._resolveFilename(request, module);
};

require.main = process.mainModule;
require.extensions = Module._extensions;
require.cache = Module._cache;

// Inject feflow variable
script = '(function(exports, require, module, __filename, __dirname, feflow){' +
script + '});';

const fn = vm.runInThisContext(script, path);

return fn(module.exports, require, module, path, pathFn.dirname(path), self);
}).asCallback(callback);
}
}

module.exports = Feflow;
53 changes: 53 additions & 0 deletions lib/core/load_plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

const pathFn = require('path');
const fs = require('hexo-fs');
const Promise = require('bluebird');
const chalk = require('chalk');

module.exports = function(ctx) {
return Promise.all([
loadModules(ctx)
]);
};

function loadModules(ctx) {
const self = this;
const pluginDir = ctx.pluginDir;

return loadModuleList(ctx).map(function(name) {
let path = require.resolve(pathFn.join(pluginDir, name));

// Load plugins
return ctx.loadPlugin(path).then(function() {
ctx.log.debug('Plugin loaded: %s', chalk.magenta(name));
}).catch(function(err) {
ctx.log.error({err: err}, 'Plugin load failed: %s', chalk.magenta(name));
});
});
}

function loadModuleList(ctx) {
const packagePath = pathFn.join(ctx.baseDir, 'package.json');
const pluginDir = ctx.pluginDir;

// Make sure package.json exists
return fs.exists(packagePath).then(function(exist) {
if (!exist) return [];

// Read package.json and find dependencies
return fs.readFile(packagePath).then(function(content) {
const json = JSON.parse(content);
const deps = json.dependencies || json.devDependencies || {};

return Object.keys(deps);
});
}).filter(function(name) {
// Ignore plugins whose name is not started with "feflow-plugin-"
if (!/^feflow-plugin-|^@[^/]+\/feflow-plugin-/.test(name)) return false;

// Make sure the plugin exists
const path = pathFn.join(pluginDir, name);
return fs.exists(path);
});
}
44 changes: 44 additions & 0 deletions lib/feflow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

const Feflow = require('./core');
const minimist = require('minimist');

function entry(args) {
args = minimist(process.argv.slice(2));

const feflow = new Feflow(args);
const log = feflow.log;

function handleError(err) {
if (err) {
log.fatal(err);
}

process.exit(2);
}

return feflow.init().then(function() {
let cmd = '';

if (!args.h && !args.help) {
cmd = args._.shift();

if (cmd) {
let c = feflow.cmd.get(cmd);
if (!c) cmd = 'help';
} else {
cmd = 'help';
}
} else {
cmd = 'help';
}

return feflow.call(cmd, args).then(function() {

}).catch(function(err) {
console.log(err);
});
}).catch(handleError);
}

module.exports = entry;
Empty file removed lib/index.js
Empty file.
26 changes: 26 additions & 0 deletions lib/plugins/console/help.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';
const meow = require('meow');

function helpConsole(args) {
const cli = meow(`
Usage: feflow [options] [command]
Commands:
init Choose a scaffold to initialize project.
install <plugin> Install a plugin or a yeoman generator.
publish Publish files to cdn or offline package.
jb Publish to jb when in development.
ars --env Publish to ars code platform, env is daily, pre or prod.
Options:
--version, -[vV] Print version and exit successfully.
--help, -[hH] Print this help and exit successfully.
Report bugs to http://git.code.oa.com/feflow/discussion/issues.
`);

return cli.showHelp(1);
}

module.exports = helpConsole;
10 changes: 10 additions & 0 deletions lib/plugins/console/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';

module.exports = function(ctx) {

const cmd = ctx.cmd;

cmd.register('help', 'Get help on a command.', {}, require('./help'));

cmd.register('version', 'Display version information.', {}, require('./version'));
};
22 changes: 22 additions & 0 deletions lib/plugins/console/version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

const os = require('os');
const Promise = require('bluebird');

function versionConsole(args) {
const versions = process.versions;
const keys = Object.keys(versions);
let key = '';

console.log('feflow:', this.version);
console.log('os:', os.type(), os.release(), os.platform(), os.arch());

for (let i = 0, len = keys.length; i < len; i++) {
key = keys[i];
console.log('%s: %s', key, versions[key]);
}

return Promise.resolve();
}

module.exports = versionConsole;
Loading

0 comments on commit febe1c3

Please sign in to comment.