boot-stacker
Version:
Boot your stack. Simple, flexible, magical manager for running tasks and applications of all sorts.
315 lines (259 loc) • 8.59 kB
text/coffeescript
_ = require 'lodash'
util = require './util'
proc_lib = require './proc_lib'
repl_lib = require './repl_lib'
{run_cmd} = require './run_cmd'
state_lib = require './state_lib'
task_config_lib = require './task_config'
task_lib = require './task_lib'
_log = (args...) -> util.debug_log.apply null, [__filename].concat args
invalid_command_invocation = (cmd) ->
util.log_error "usage: #{cmd.usage}"
Promise.resolve()
################################################################################
# Process Status
#
# TODO: what cool stuff can we show?
# - Runtime
# - PID
# - Git branch?!
# - Version #??
################################################################################
processes_running = ->
util.print "What's running".cyan
util.print if _.keys(proc_lib.all_procs()).length
("#{k}\n" for k, v of proc_lib.all_procs()).join('')
else
'No running procs!'.bold.cyan
util.print if _.keys(proc_lib.all_daemons()).length
"\n#{'Daemons'.cyan}\n#{(k+'\n' for k, v of proc_lib.all_daemons()).join('')}"
else
'No running daemons!'.bold.cyan
repl_lib.add_command
name: 'ps'
help: 'status of running processes'
fn: processes_running
################################################################################
# Daemons
################################################################################
repl_lib.add_command
name: 'ds'
help: 'status of daemons'
fn: ->
util.print 'Daemons'.cyan
util.print if _.keys(proc_lib.all_daemons()).length
"#{(k+'\n' for k, v of proc_lib.all_daemons()).join('')}"
else
'No running daemons!'.bold.cyan
repl_lib.add_command
name: 'nuke'
help: 'kill -9 java'
fn: (task) ->
child_process.exec 'killall -9 java', (error, stdout, stderr) ->
if error
util.print error.message.red
return
util.print 'Nuking All Javas!'.magenta
util.print stdout
if stderr
util.print stderr.red
repl_lib.add_command
name: 'help'
alias: 'h'
help: "'help' helps you get help for commands such as 'help'"
fn: ->
util.print 'available commands'.cyan.bold
strs = for {name, help, usage, alias} in _.values(repl_lib.get_commands())
str = "#{name.cyan}: #{help}\n"
str += "usage: #{usage}\n" if usage
str += "alias: #{alias}\n" if alias
str
util.print strs.join '\n'
repl_lib.add_command
name: 'state'
alias: 'env'
help: 'print information about the stacker state'
fn: ->
util.print 'STACKER STATE'.cyan.bold
util.print util.beautify_obj(state_lib.get_stacker_state())
repl_lib.add_command
name: 'set'
alias: 's'
help: 'set environment variable'
usage: 'set KEY VALUE'
fn: (k, v) ->
unless k? and v?
return invalid_command_invocation @
state_lib.set_stacker_state "#{k}": v
################################################################################
# REPL TELL
#
# tell someone to do something
#
# usage: tell [TARGET(S)] [SOMETHING TO DO]
# e.g. tell alm grunt clean build
#
# looks for a task by name, otherwise looks for a directory in ~/projects
#
# can use a comma-separated list of targets
# e.g. tell alm,appsdk,app-catalog,churro grunt clean build
################################################################################
# (string, [string]) -> null
tell_target = (target, cmd) ->
try
path = if task_config_lib.task_exists(target)
task_config_lib.read_task_property(target, 'cwd') ? process.cwd()
else if fs.statSync("#{process.env.HOME}/projects/#{target}")?.isDirectory()
"#{process.env.HOME}/projects/#{target}"
catch e
unless e.code is 'ENOENT' # handle missing directory below
_log e.stack
throw e
unless path?
util.print "'#{target}' is not a task name or a directory in ~/projects".red
return
run_cmd
cmd: cmd
cwd: path
repl_lib.add_command
name: 'tell'
alias: 't'
tab_complete: (args) ->
['ronald', 'mc', 'donald']
help: 'tell someone to do something (e.g. tell alm grunt clean build)'
usage: 'tell TASK COMMAND'
fn: (target, cmd...) ->
unless target? and cmd.length
return invalid_command_invocation @
targets = target.split ','
_.map targets, (target) -> tell_target target, cmd
# (string, [string]) -> null
cleanup_task = (target) ->
unless target?
return invalid_command_invocation @
task_name = task_config_lib.resolve_task_name target
unless task_config_lib.task_exists task_name
util.log_error "Task does not exist: #{task_name}"
return Promise.resolve()
task_config = task_config_lib.get_task_config task_name
task_config.cleanup.call(task_config).then ([code, signal]) ->
if code is 0
util.print "#{'Cleaned up'.green} #{task_name.cyan} #{'successfully!'.green}"
else
util.print "#{'Clean up task failed for'.yellow} #{task_name.cyan}#{'.'.yellow}"
repl_lib.add_command
name: 'cleanup'
alias: 'cu'
help: 'run cleanup for a task'
usage: 'cleanup TASK'
fn: cleanup_task
# (string, [string]) -> null
is_daemon_running = (target) ->
unless target?
return invalid_command_invocation @
task_name = task_config_lib.resolve_task_name target
unless task_config_lib.task_exists task_name
util.log_error "Task does not exist: #{task_name}"
return Promise.resolve()
task_config = task_config_lib.get_task_config task_name
stop_indicator = util.start_progress_indicator()
util.print "Checking to see if #{task_name.cyan} is running..."
task_config.is_running.call(task_config).then (is_running) ->
stop_indicator()
if is_running
util.print "#{task_name.cyan} #{'is running'.green}"
else
util.print "#{task_name.cyan} #{'is not running'.green}"
repl_lib.add_command
name: 'running'
alias: 'r?'
help: 'is daemon runnning?'
usage: 'running TASK'
fn: is_daemon_running
repl_lib.add_command
name: 'tasks'
help: 'print all tasks'
fn: ->
util.print 'tasks:', (_(task_config_lib.get_task_config_map()).keys().value().join ' ').cyan
repl_lib.add_command
name: 'kill'
alias: 'k'
help: 'kill a task'
usage: 'kill TASK'
fn: (target) ->
unless target?
return invalid_command_invocation @
task_lib.kill_task target
repl_lib.add_command
name: 'killall'
alias: 'ka'
help: 'kill all running processes'
fn: task_lib.kill_running_tasks
repl_lib.add_command
name: 'restart'
alias: 'rs'
help: 'restart a task'
usage: 'restart TASK'
fn: (target) ->
unless target?
return invalid_command_invocation @
util.print "Restarting #{target}..."
task_lib.kill_task(target).then ->
task_lib.start_task(target)
repl_lib.add_command
name: 'run'
alias: 'r'
help: 'start multiple tasks'
usage: 'run TASKS'
fn: (tasks...) ->
unless tasks.length
return invalid_command_invocation @
task_lib.run_tasks tasks
repl_lib.add_command
name: 'setenv'
help: 'set a shell environment variable'
usage: 'setenv KEY VALUE'
fn: (k, v) ->
unless k? and v?
return invalid_command_invocation @
util.print 'setting shell environment variable'.cyan.bold, "#{k}".blue.bold, 'to'.cyan.bold, "#{v}".magenta
state = state_lib.get_stacker_state()
state.shell_env ?= {}
state.shell_env[k] = v
state_lib.set_stacker_state state
repl_lib.add_command
name: 'history'
help: 'Show n lines of command history'
usage: 'history [n]'
fn: (n = 10) ->
util.print "Stacker History".cyan.bold
util.print "#{(repl_lib.get_repl().rli.history[0...n].join '\n')}\n"
################################################################################
# exit stacker
################################################################################
stacker_exit = ->
# max timeout of 4s
_.delay ->
console.log 'ALL TASKS DID NOT EXIT IN 4 SECONDS'.red
console.log "Left dangling procs with PIDs #{_.map(proc_lib.all_procs(), 'pid').join ', '}".yellow
process.exit 1
, 4000
# _.delay process.exit, 4000
# TODO: print PIDs of processes that could not be killed in time
task_lib.kill_running_tasks().then ->
util.print 'Killed running tasks!'.green
t = 0 ; delta = 200 ; words = 'Going To Sleep Mode'.split ' '
_.map words, (word) ->
setTimeout (-> process.stdout.write "#{word.blue.bold} "), t += delta
_.delay (-> process.exit 0), words.length * delta
repl_lib.add_command
name: 'exit'
help: 'exit stacker'
fn: stacker_exit
start_repl = ->
repl_lib.start()
.on 'exit', stacker_exit
exports = {
start_repl
}
module.exports[k] = v for k, v of exports