Skip to content

Commit

Permalink
feat: support custom command and overriding command
Browse files Browse the repository at this point in the history
redesign cli load sequence and sub command boot logic.
now, brickyard3 as a global command, can run anywhere without a sub command.
introduce `bluebird` for using Promise
  • Loading branch information
e-cloud committed Sep 19, 2016
1 parent fd4a6ce commit 8c5b503
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 91 deletions.
116 changes: 63 additions & 53 deletions base/brickyard-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,70 @@
'use strict'

const Liftoff = require('liftoff')
const argv = require('minimist')(process.argv.slice(2))
const Command = require('commander').Command
const packageInfo = require('../package.json')
const butil = require('../lib/util')

const app = new Liftoff({
name: packageInfo.name,
processTitle: packageInfo.name,
moduleName: packageInfo.name,
configName: 'by-conf',
extensions: {
'.js': null,
'.json': null
},
v8flags: ['--harmony']
})

app.launch({
// cwd: argv.r || argv.root,
configPath: argv.config
}, function (env) {
let brickyard
if (!env.modulePath) {
brickyard = require('../')
} else {
brickyard = require(env.modulePath)
}

const cli = brickyard.cli

const commander = new Command(packageInfo.name)

if (argv.verbose) {
brickyard.setLogLevel('debug')
} else {
brickyard.setLogLevel(argv.loglevel)
}

brickyard.load(env.configPath)

cli.load(commander, packageInfo, commandRunner)

commander.parse(process.argv)

/**
* a callback runner invoke when a subcommand is at action,
* and then invoke the subcommand's run with runtime object
*
* @param options
*/
function commandRunner(options) {
const cmdOptions = butil.assignWithValid({}, options, commander.opts())
const command = argv._[0]

cli.commands[command].run(brickyard.hatchRuntime(cmdOptions))
}
})
const rootCmd = initRootCmd(packageInfo)

rootCmd.parse(process.argv)

boot(rootCmd.opts())

function boot(argv) {
const app = new Liftoff({
name: packageInfo.name,
processTitle: packageInfo.name,
moduleName: packageInfo.name,
configName: 'by-conf',
extensions: {
'.js': null,
'.json': null
},
v8flags: ['--harmony']
})

app.launch({
configPath: argv.config
}, env => {
let brickyard = !env.modulePath ? require('../') : require(env.modulePath)

if (argv.verbose) {
brickyard.setLogLevel('debug')
} else {
brickyard.setLogLevel(argv.loglevel)
}

brickyard.cli.load(rootCmd, env.configPath ? require(env.configPath).commands : null)
.spread((cmdName, options) => {
const cmdOptions = butil.assignWithValid({}, options, rootCmd.opts())
const targetCmd = brickyard.cli.commands[cmdName]

brickyard.load(env.configPath)

targetCmd.run(brickyard.hatchRuntime(cmdOptions))
})
.catch(e => {
throw e
})

rootCmd.parse(process.argv)
})
}

function initRootCmd(pkgInfo) {
const cmd = new Command(pkgInfo.name)

cmd
.version(pkgInfo.version, '-v, --version')
.alias('by')
.arguments('[cmd]')
.description(pkgInfo.description)
.usage('[cmd] [options]')
.option('--config <path>', 'config path')
.option('--no-color', 'output without color')
.option('--loglevel <level>', 'output log verbosity. Available levels are: trace,debug,info,warn,error,fatal')
.option('-V, --verbose', 'output log verbosely. Same as debug level. Prior to loglevel argument', Boolean, false)

return cmd
}
5 changes: 3 additions & 2 deletions lib/brickyard.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ const _ = require('lodash')

const butil = require('./util')
const logging = require('./logger')
const logger = logging.getLogger('Brickyard')
const configLoader = require('./configLoader')
const programLoader = require('./programLoader')
const pluginLoader = require('./pluginLoader')
const path = require('path')

const logger = logging.getLogger('Brickyard')

class Brickyard extends EventEmitter {
constructor() {
super()
Expand Down Expand Up @@ -101,7 +102,7 @@ function compoundPrograms(programs, target) {
const programsConfig = _.chain(targetPrograms)
.map('config')
.compact()
.reduce(function (sum, val) {
.reduce((sum, val) => {
Object.assign(sum, val)
return sum
}, {})
Expand Down
83 changes: 48 additions & 35 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const _ = require('lodash')
const packageLoader = require('./packageLoader')
const path = require('path')
const logger = require('log4js').getLogger('CLI')
const Promise = require('bluebird')

const cli = {
commands: {}
Expand All @@ -19,54 +20,66 @@ const cli = {
* pass a callback to the command so when the command is
* triggered, brickyard can do something to run the command.
*
* @param {Command} rootcmd
* @param {Object} packageInfo
* @param {function} [commandRunner]
* @param {Command} rootCmd
* @param userCommands
*/
cli.load = function (rootcmd, packageInfo, commandRunner) {
logger.trace('register root commands')

rootcmd
.version(packageInfo.version, '-v, --version')
.alias('by')
.arguments('[cmd]')
.description(packageInfo.description)
.usage('[cmd] [options]')
// .option('--root', 'project dir')
.option('--config <path>', 'config path')
.option('--no-color', 'output without color')
.option('--loglevel <level>', 'output log verbosity')
.option('--verbose', 'output log verbosity', Boolean, false)

loadCommands(rootcmd, commandRunner)
cli.load = function (rootCmd, userCommands) {
return new Promise((resolve) => loadCommands(rootCmd, userCommands, resolve))
}

module.exports = Object.seal(cli)

function loadCommands(rootcmd, commandRunner) {
const commandsRepo = 'node_modules/brickyard-command-*/'
const commandsPathPattern = path.join(process.cwd(), commandsRepo, 'package.json')
function loadCommands(rootCmd, userCommands, resolve) {
logger.trace('batch register commands')

if (Array.isArray(userCommands)) {
loadSpecificCommands(rootCmd, userCommands, resolve)
} else {
autoDetectCommands(rootCmd, resolve)
}
}

function loadSpecificCommands(rootCmd, userCommands, resolve) {
logger.trace('loading specific commands')
const commandsConfig = userCommands.reduce(function constructCmdConfig(result, cmd) {
if (_.isObject(cmd)) {
if (!path.isAbsolute(cmd.path)) {
throw Error('Path inside command object must be absolute path.')
} else {
result.push(cmd)
}
} else {
result.push({ name: cmd })
}

return result
}, [])

_loadCommands(rootCmd, commandsConfig, resolve)
}

function autoDetectCommands(rootCmd, resolve) {
logger.trace('auto-detecting commands')
const commandsRepo = 'brickyard-command-*'
const commandsPathPattern = path.join(process.cwd(), 'node_modules', commandsRepo, 'package.json')
const commandsConfig = packageLoader.getPackages(commandsPathPattern)

logger.trace('batch register commands')
_loadCommands(rootCmd, commandsConfig, resolve)
}

function _loadCommands(rootCmd, commandsConfig, resolve) {
logger.trace('loading the actual commands')

_.forOwn(commandsConfig, function (cmdConfig, name) {
const cmdModule = require(cmdConfig.path)
const cmdName = name.split('-')[2]
_.forOwn(commandsConfig, function (cmdConfig) {
const cmdModule = require(cmdConfig.path || cmdConfig.name)
const cmdName = cmdConfig.name.split('-')[2]

if (!cmdName) {
throw new Error('Invalid brickyard command module, it should be `brickyard-command-***`')
throw new Error('Invalid brickyard command module, it should be named by `brickyard-command-***`.')
}
const cmd = cmdModule.register(rootcmd.command(cmdName), commandRunner)

if (cli.commands[cmdName]) {
throw new Error(`Duplicated command ${name} - ${cmdConfig.path}`)
}
cmdModule.register(rootCmd.command(cmdName), result => resolve([cmdName, result]))

cli.commands[cmdName] = cmdModule

if (cmd && cmd._alias) {
cli.commands[cmd._alias] = cmdModule
}
})
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"liftoff": "^2.2.0",
"lodash": "^4.6.1",
"log4js": "^0.6.33",
"minimist": "^1.2.0"
"bluebird": "^3.4.0"
},
"devDependencies": {
"brickyard-command-init": "*",
Expand Down

0 comments on commit 8c5b503

Please sign in to comment.