@seneca/repl
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -10,3 +10,3 @@ #!/usr/bin/env node | ||
const vorpal = Vorpal() | ||
const vorpal = Vorpal({history:{ignore_mode:true}}) | ||
@@ -32,2 +32,3 @@ const connection = {} | ||
var cmd = args+'\n' | ||
connection.sock.write(cmd) | ||
@@ -51,2 +52,3 @@ callback() | ||
else { | ||
vorpal.history('seneca~'+host+'~'+port) | ||
vorpal.exec('repl') | ||
@@ -53,0 +55,0 @@ } |
{ | ||
"name": "@seneca/repl", | ||
"description": "Provides a client and server REPL for Seneca microservice systems.", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"main": "repl.js", | ||
@@ -20,3 +20,3 @@ "license": "MIT", | ||
"scripts": { | ||
"test": "lab -P test -v -t 70", | ||
"test": "lab -P test -v -t 75 -r console -o stdout -r html -o test/coverage.html", | ||
"coveralls": "lab -s -P test -r lcov | coveralls", | ||
@@ -40,8 +40,9 @@ "coverage": "lab -v -P test -t 70 -r html > coverage.html", | ||
"dependencies": { | ||
"@seneca/vorpal": "^2.0.2" | ||
"@hapi/hoek": "^9.0.0", | ||
"@seneca/vorpal": "^2.1.0" | ||
}, | ||
"devDependencies": { | ||
"@hapi/code": "^7.0.0", | ||
"@hapi/code": "^8.0.1", | ||
"@hapi/joi": "^15.1.1", | ||
"@hapi/lab": "^22.0.2", | ||
"@hapi/lab": "^21.0.0", | ||
"acorn": "^7.1.0", | ||
@@ -48,0 +49,0 @@ "async": "^3.1.0", |
@@ -107,10 +107,11 @@ ![Seneca](http://senecajs.org/files/assets/seneca-logo.png) | ||
* `list`: list local patterns | ||
* `list <pin>`: list local patterns, optionally narrowed by `pin` | ||
* `tree`: show local patterns in tree format | ||
* `stats`: print local statistics | ||
* `stats/full`: print full local statistics | ||
* `stats full`: print full local statistics | ||
* `exit` or `quit`: exit the repl session | ||
* `last`: run last command again | ||
* `history`: print command history | ||
* `set <path> <value>`: set a seneca option, e.g: `set debug.deprecation true`. Use `seneca.options()` to get options | ||
* `set <path> <value>`: set a seneca option, e.g: `set debug.deprecation true` | ||
* `get <path>`: get a seneca option | ||
* `alias <name> <cmd>`: define a new alias | ||
@@ -120,7 +121,9 @@ * `trace`: toggle IN/OUT tracing of submitted messages | ||
* `log match <literal>`: when logging is enabled, only print lines matching the provided literal string | ||
* `depth <number>`: set depth of Util.inspect printing | ||
## Contributing | ||
The [Senecajs org][] encourage open participation. If you feel you can help in any way, be it with | ||
documentation, examples, extra testing, or new features please get in touch. | ||
The [Senecajs org][] encourages open participation. If you feel you | ||
can help in any way, be it with documentation, examples, extra | ||
testing, or new features please get in touch. | ||
@@ -136,3 +139,3 @@ ## Test | ||
## License | ||
Copyright (c) 2015-2017, Richard Rodger and other contributors. | ||
Copyright (c) 2015-2020, Richard Rodger and other contributors. | ||
Licensed under [MIT][]. | ||
@@ -139,0 +142,0 @@ |
513
repl.js
@@ -0,12 +1,14 @@ | ||
/* Copyright © 2015-2020 Richard Rodger and other contributors, MIT License. */ | ||
'use strict' | ||
// TODO: implement cmd for seneca.make('core/fixture').load$('qazwsx',(e,x)=>console.log(x.data$())) to show ent data | ||
// NOTE: vorpal is not used server-side to keep things lean | ||
// Load modules | ||
var Net = require('net') | ||
var Repl = require('repl') | ||
var Util = require('util') | ||
var Vm = require('vm') | ||
const Net = require('net') | ||
const Repl = require('repl') | ||
const Util = require('util') | ||
const Vm = require('vm') | ||
const Hoek = require('@hapi/hoek') | ||
module.exports = repl | ||
@@ -18,4 +20,6 @@ module.exports.defaults = { | ||
alias: { | ||
list: 'seneca.list()', | ||
stats: 'seneca.stats()', | ||
'stats full': 'seneca.stats({summary:false})', | ||
// DEPRECATED | ||
'stats/full': 'seneca.stats({summary:false})', | ||
@@ -25,5 +29,26 @@ | ||
tree: 'seneca.root.private$.actrouter' | ||
}, | ||
inspect: { | ||
}, | ||
cmds: { | ||
// custom cmds | ||
} | ||
} | ||
const intern = repl.intern = make_intern() | ||
const default_cmds = { | ||
get: intern.cmd_get, | ||
depth: intern.cmd_depth, | ||
plain: intern.cmd_plain, | ||
quit: intern.cmd_quit, | ||
list: intern.cmd_list, | ||
history: intern.cmd_history, | ||
log: intern.cmd_log, | ||
set: intern.cmd_set, | ||
alias: intern.cmd_alias, | ||
trace: intern.cmd_trace, | ||
} | ||
function repl(options) { | ||
@@ -33,4 +58,7 @@ var seneca = this | ||
var cmd_map = Object.assign({},default_cmds,options.cmds) | ||
seneca.init(function(reply) { | ||
var server = start_repl(seneca, options) | ||
var server = intern.start_repl(seneca, options, cmd_map) | ||
@@ -65,223 +93,330 @@ server.on('listening', function() { | ||
function start_repl(seneca, options) { | ||
var alias = options.alias | ||
function make_intern() { | ||
return { | ||
start_repl: function (seneca, options, cmd_map) { | ||
var alias = options.alias | ||
var server = Net.createServer(function(socket) { | ||
socket.on('error', function(err) { | ||
sd.log.debug('repl-socket', err) | ||
}) | ||
var server = Net.createServer(function(socket) { | ||
socket.on('error', function(err) { | ||
sd.log.debug('repl-socket', err) | ||
}) | ||
var r = Repl.start({ | ||
prompt: 'seneca ' + seneca.version + ' ' + seneca.id + '> ', | ||
input: socket, | ||
output: socket, | ||
terminal: false, | ||
useGlobal: false, | ||
eval: evaluate | ||
}) | ||
var sd = seneca.root.delegate({ repl$: true, fatal$: false }) | ||
r.on('exit', function() { | ||
socket.end() | ||
}) | ||
var r = Repl.start({ | ||
prompt: 'seneca ' + seneca.version + ' ' + seneca.id + '> ', | ||
input: socket, | ||
output: socket, | ||
terminal: false, | ||
useGlobal: false, | ||
eval: evaluate | ||
}) | ||
r.on('exit', function() { | ||
socket.end() | ||
}) | ||
var act_trace = false | ||
var act_index_map = {} | ||
var act_index = 1000000 | ||
function fmt_index(i) { | ||
return ('' + i).substring(1) | ||
} | ||
r.on('error', function(err) { | ||
sd.log.debug('repl', err) | ||
}) | ||
var sd = seneca.root.delegate({ repl$: true, fatal$: false }) | ||
Object.assign(r.context, { | ||
// NOTE: don't trigger funnies with a .inspect property | ||
inspekt: intern | ||
.make_inspect(r.context,{...options.inspect, depth:options.depth}), | ||
socket: socket, | ||
s: sd, | ||
seneca: sd, | ||
plain: false, | ||
history: [], | ||
log_capture: false, | ||
log_match: null, | ||
alias: alias, | ||
act_trace: false, | ||
act_index_map: {}, | ||
act_index: 1000000, | ||
}) | ||
r.on('error', function(err) { | ||
sd.log.debug('repl', err) | ||
}) | ||
sd.on_act_in = intern.make_on_act_in(r.context) | ||
sd.on_act_out = intern.make_on_act_out(r.context) | ||
sd.on_act_err = intern.make_on_act_err(r.context) | ||
sd.on('log', intern.make_log_handler(r.context)) | ||
sd.on_act_in = function on_act_in(actdef, args, meta) { | ||
if(!act_trace) return; | ||
var actid = (meta || args.meta$ || {}).id | ||
socket.write( | ||
'IN ' + | ||
fmt_index(act_index) + | ||
': ' + | ||
Util.inspect(sd.util.clean(args)) + | ||
' # ' + | ||
actid + | ||
' ' + | ||
actdef.pattern + | ||
' ' + | ||
actdef.id + | ||
' ' + | ||
actdef.action + | ||
' ' + | ||
(actdef.callpoint ? actdef.callpoint : '') + | ||
'\n' | ||
) | ||
act_index_map[actid] = act_index | ||
act_index++ | ||
} | ||
function evaluate(cmdtext, context, filename, respond) { | ||
const inspect = context.inspekt | ||
var cmd_history = context.history | ||
cmdtext = cmdtext.trim() | ||
sd.on_act_out = function on_act_out(actdef, out, meta) { | ||
if(!act_trace) return; | ||
var actid = (meta || out.meta$ || {}).id | ||
if ('last' === cmdtext && 0 < cmd_history.length) { | ||
cmdtext = cmd_history[cmd_history.length - 1] | ||
} else { | ||
cmd_history.push(cmdtext) | ||
} | ||
out = out && out.entity$ | ||
? out | ||
: Util.inspect(sd.util.clean(out), { depth: options.depth }) | ||
if (alias[cmdtext]) { | ||
cmdtext = alias[cmdtext] | ||
} | ||
var cur_index = act_index_map[actid] | ||
socket.write('OUT ' + fmt_index(cur_index) + ': ' + out + '\n') | ||
} | ||
var m = cmdtext.match(/^(\S+)/) | ||
var cmd = m && m[1] | ||
sd.on_act_err = function on_act_err(actdef, err, meta) { | ||
if(!act_trace) return; | ||
var actid = (meta || err.meta$ || {}).id | ||
var argtext = 'string' === typeof(cmd) ? cmdtext.substring(cmd.length) : '' | ||
// NOTE: alias can also apply just to command | ||
if (alias[cmd]) { | ||
cmd = alias[cmd] | ||
} | ||
var cmd_func = cmd_map[cmd] | ||
// console.log('CMD', cmd, !!cmd_func) | ||
if(cmd_func) { | ||
return cmd_func(cmd, argtext, context, options, respond) | ||
} | ||
if (!execute_action(cmdtext)) { | ||
execute_script(cmdtext) | ||
} | ||
if (actid) { | ||
var cur_index = act_index_map[actid] | ||
socket.write('ERR ' + fmt_index(cur_index) + ': ' + err.message + '\n') | ||
} | ||
} | ||
function execute_action(cmdtext) { | ||
try { | ||
var args = seneca.util.Jsonic(cmdtext) | ||
context.s.act(args, function(err, out) { | ||
if(out && !r.context.act_trace) { | ||
out = out && out.entity$ | ||
? out | ||
: context.inspekt(sd.util.clean(out)) | ||
socket.write(out + '\n') | ||
} | ||
else if(err) { | ||
socket.write(context.inspekt(err) + '\n') | ||
} | ||
}) | ||
return true | ||
} catch (e) { | ||
// Not jsonic format, so try to execute as a script | ||
// TODO: check actual jsonic parse error so we can give better error | ||
// message if not | ||
return false | ||
} | ||
} | ||
var log_capture = false | ||
var log_match = null | ||
sd.on('log',function(data) { | ||
if(log_capture) { | ||
var out = sd.__build_test_log__$$ ? | ||
sd.__build_test_log__$$(this,'test',data) : | ||
Util.inspect(data).replace(/\n/g, ' ') | ||
function execute_script(cmdtext) { | ||
try { | ||
var script = Vm.createScript(cmdtext, { | ||
filename: filename, | ||
displayErrors: false | ||
}) | ||
var result = script.runInContext(context, { displayErrors: false }) | ||
if(null == log_match || -1 < out.indexOf(log_match)) { | ||
socket.write('LOG: '+out) | ||
result = result === seneca ? null : result | ||
respond(null, result) | ||
} catch (e) { | ||
return respond(e.message) | ||
} | ||
} | ||
} | ||
} | ||
}) | ||
}).listen(options.port, options.host) | ||
r.context.s = r.context.seneca = sd | ||
return server | ||
}, | ||
parse_option: function(optpath, val) { | ||
optpath += '.' | ||
var cmd_history = [] | ||
var part = /([^.]+)\.+/g | ||
var m | ||
var out = {} | ||
var cur = out | ||
var po = out | ||
var pn | ||
function evaluate(cmdtext, context, filename, callback) { | ||
var m = cmdtext.match(/^(\S+)/) | ||
var cmd = m && m[1] | ||
while (null != (m = part.exec(optpath))) { | ||
cur[m[1]] = {} | ||
po = cur | ||
pn = m[1] | ||
cur = cur[m[1]] | ||
} | ||
po[pn] = val | ||
return out | ||
}, | ||
if ('last' === cmd) { | ||
cmd = cmd_history[cmd_history.length - 1] | ||
} else { | ||
cmd_history.push(cmd) | ||
make_inspect: function(context, inspect_options) { | ||
return (x) => { | ||
if(context.plain) { | ||
x = JSON.parse(JSON.stringify(x)) | ||
} | ||
return Util.inspect(x, inspect_options) | ||
} | ||
}, | ||
if ('quit' === cmd || 'exit' === cmd) { | ||
socket.end() | ||
} else if ('trace' === cmd) { | ||
act_trace = !act_trace | ||
return callback() | ||
} else if ('log' === cmd) { | ||
log_capture = !log_capture | ||
fmt_index: function (i) { | ||
return ('' + i).substring(1) | ||
}, | ||
if(!log_capture) { | ||
log_match = null | ||
make_log_handler: function(context) { | ||
return function log_handler(data) { | ||
if(context.log_capture) { | ||
var seneca = context.seneca | ||
var out = | ||
seneca.__build_test_log__$$ ? | ||
seneca.__build_test_log__$$(seneca,'test',data) : | ||
context.inspekt(data).replace(/\n/g, ' ') | ||
if(null == context.log_match || | ||
-1 < out.indexOf(context.log_match)) { | ||
context.socket.write('LOG: '+out) | ||
} | ||
} | ||
else if(m = cmdtext.match(/^log\s+match\s+(.*)/)) { | ||
log_match = m[1] | ||
} | ||
return callback() | ||
} else if ('history' === cmd) { | ||
return callback(cmd_history.join('\n')) | ||
} else if ('set' === cmd) { | ||
m = cmdtext.match(/^(\S+)\s+(\S+)\s+(\S+)/) | ||
} | ||
}, | ||
if (m) { | ||
var setopt = parse_option(m[2], seneca.util.Jsonic('$:' + m[3]).$) | ||
context.s.options(setopt) | ||
make_on_act_in: function(context) { | ||
return function on_act_in(actdef, args, meta) { | ||
if(!context.act_trace) return; | ||
var actid = (meta || args.meta$ || {}).id | ||
context.socket.write( | ||
'IN ' + | ||
intern.fmt_index(context.act_index) + | ||
': ' + | ||
context.inspekt(context.seneca.util.clean(args)) + | ||
' # ' + | ||
actid + | ||
' ' + | ||
actdef.pattern + | ||
' ' + | ||
actdef.id + | ||
' ' + | ||
actdef.action + | ||
' ' + | ||
(actdef.callpoint ? actdef.callpoint : '') + | ||
'\n' | ||
) | ||
context.act_index_map[actid] = context.act_index | ||
context.act_index++ | ||
} | ||
}, | ||
if (setopt.repl) { | ||
options = context.s.util.deepextend(options, setopt.repl) | ||
} | ||
make_on_act_out: function(context) { | ||
return function on_act_out(actdef, out, meta) { | ||
if(!context.act_trace) return; | ||
var actid = (meta || out.meta$ || {}).id | ||
return callback() | ||
} else { | ||
return callback('ERROR: expected set <path> <value>') | ||
} | ||
} else if ('alias' === cmd) { | ||
m = cmdtext.match(/^(\S+)\s+(\S+)\s+(.+)[\r\n]+$/) | ||
out = out && out.entity$ | ||
? out | ||
: context.inspekt(context.seneca.util.clean(out)) | ||
var cur_index = context.act_index_map[actid] | ||
context.socket.write('OUT ' + intern.fmt_index(cur_index) + ': ' + out + '\n') | ||
} | ||
}, | ||
make_on_act_err: function(context) { | ||
return function on_act_err(actdef, err, meta) { | ||
if(!context.act_trace) return; | ||
var actid = (meta || err.meta$ || {}).id | ||
if (m) { | ||
alias[m[2]] = m[3] | ||
return callback() | ||
} else { | ||
return callback('ERROR: expected alias <name> <command>') | ||
if (actid) { | ||
var cur_index = context.act_index_map[actid] | ||
context.socket.write('ERR ' + | ||
intern.fmt_index(cur_index) + ': ' + | ||
err.message + '\n') | ||
} | ||
} else if (alias[cmd]) { | ||
cmd = alias[cmd] | ||
} | ||
}, | ||
cmd_get: function(cmd, argtext, context, options, respond) { | ||
var option_path = argtext.trim() | ||
var options = context.seneca.options() | ||
var out = Hoek.reach(options,option_path) | ||
return respond(null,out) | ||
}, | ||
if (!execute_action(cmd)) { | ||
execute_script(cmd) | ||
cmd_depth: function(cmd, argtext, context, options, respond) { | ||
var depth = parseInt(argtext,10) | ||
depth = isNaN(depth) ? null : depth | ||
context.inspekt = | ||
intern.make_inspect(context, {...options.inspect, depth:depth}) | ||
return respond(null,'Inspection depth set to '+depth) | ||
}, | ||
cmd_plain: function(cmd, argtext, context, options, respond) { | ||
context.plain = !context.plain | ||
return respond() | ||
}, | ||
cmd_quit: function(cmd, argtext, context, options, respond) { | ||
context.socket.end() | ||
}, | ||
cmd_list: function(cmd, argtext, context, options, respond) { | ||
var narrow = context.seneca.util.Jsonic(argtext) | ||
respond(null, context.seneca.list(narrow)) | ||
}, | ||
cmd_history: function(cmd, argtext, context, options, respond) { | ||
return respond(null,context.history.join('\n')) | ||
}, | ||
cmd_log: function(cmd, argtext, context, options, respond) { | ||
context.log_capture = !context.log_capture | ||
var m = null | ||
if(!context.log_capture) { | ||
context.log_match = null | ||
} | ||
function execute_action(cmd) { | ||
try { | ||
var args = seneca.util.Jsonic(cmd) | ||
context.s.act(args, function(err, out) { | ||
if(out && !act_trace) { | ||
out = out && out.entity$ | ||
? out | ||
: Util.inspect(sd.util.clean(out), { depth: options.depth }) | ||
socket.write(out + '\n') | ||
} | ||
else if(err) { | ||
socket.write(Util.inspect(err) + '\n') | ||
} | ||
}) | ||
return true | ||
} catch (e) { | ||
return false | ||
} | ||
if(m = argtext.match(/^\s*match\s+(.*)/)) { | ||
context.log_capture = true // using match always turns logging on | ||
context.log_match = m[1] | ||
} | ||
return respond() | ||
}, | ||
function execute_script(cmd) { | ||
try { | ||
var script = Vm.createScript(cmd, { | ||
filename: filename, | ||
displayErrors: false | ||
}) | ||
var result = script.runInContext(context, { displayErrors: false }) | ||
cmd_set: function(cmd, argtext, context, options, respond) { | ||
var m = argtext.match(/^\s*(\S+)\s+(\S+)/) | ||
result = result === seneca ? null : result | ||
callback(null, result) | ||
} catch (e) { | ||
return callback(e.message) | ||
if (m) { | ||
var setopt = | ||
intern.parse_option(m[1], context.seneca.util.Jsonic('$:' + m[2]).$) | ||
context.seneca.options(setopt) | ||
if (setopt.repl) { | ||
options = context.seneca.util.deepextend(options, setopt.repl) | ||
} | ||
return respond() | ||
} else { | ||
return respond('ERROR: expected set <path> <value>') | ||
} | ||
} | ||
}).listen(options.port, options.host) | ||
}, | ||
return server | ||
} | ||
cmd_alias: function(cmd, argtext, context, options, respond) { | ||
var m = argtext.match(/^\s*(\S+)\s+(.+)[\r\n]?/) | ||
if (m) { | ||
context.alias[m[1]] = m[2] | ||
return respond() | ||
} else { | ||
return respond('ERROR: expected alias <name> <command>') | ||
} | ||
}, | ||
function parse_option(optpath, val) { | ||
optpath += '.' | ||
var part = /([^.]+)\.+/g | ||
var m | ||
var out = {} | ||
var cur = out | ||
var po = out | ||
var pn | ||
while (null != (m = part.exec(optpath))) { | ||
cur[m[1]] = {} | ||
po = cur | ||
pn = m[1] | ||
cur = cur[m[1]] | ||
cmd_trace: function(cmd, argtext, context, options, respond) { | ||
context.act_trace = !context.act_trace | ||
return respond() | ||
} | ||
} | ||
po[pn] = val | ||
return out | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
21469
431
151
2
+ Added@hapi/hoek@^9.0.0
+ Added@hapi/hoek@9.3.0(transitive)
Updated@seneca/vorpal@^2.1.0