@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
144 lines (117 loc) • 5.39 kB
JavaScript
module.exports = Object.assign (repl, {
options: [ '--run', '--use' ],
shortcuts: [ '-r', '-u' ],
help: `
# SYNOPSIS
*cds repl* [ <options> ]
Launches into a read-eval-print-loop, an interactive playground to
experiment with cds' JavaScript APIs. See documentation of Node.js'
REPL for details at _http://nodejs.org/api/repl.html_
# OPTIONS
*-r | --run* <project>
Runs a cds server from a given CAP project folder, or module name.
You can then access the entities and services of the running server.
It's the same as using the repl's builtin _.run_ command.
*-u | --use* <cds feature>
Loads the given cds feature into the repl's global context. For example,
if you specify _xl_ it makes the _cds.xl_ module's methods available.
It's the same as doing _{ref,val,xpr,...} = cds.xl_ within the repl.
# EXAMPLES
*cds repl* --run bookshop
*cds repl* --run .
*cds repl* --use cds.ql
# SEE ALSO
*cds eval* to evaluate and execute JavaScript.
`})
async function repl (_, options={}) {
const GREEN = '\x1b[32m', RESET = '\x1b[0m'
const cds = require('../lib/cds')
console.log('\x1b[32m\x1b[1m'+'Welcome to cds repl v', cds.version, '\x1b[0m')
const { inspect } = require('util')
inspect.defaultOptions.colors = true
inspect.defaultOptions.depth = 11
Object.defineProperty (cds,'repl',{value:true})
await cds.service.bindings
let context={}, repl = { context, displayPrompt(){} }
if (options.run) await _run (options.run)
if (options.use) _export (...options.use.split(','))
repl = require('repl').start ({
...options,
ignoreUndefined: true,
useGlobal: true,
writer: o => {
if (!o || typeof o !== 'object') return o
return inspect(o).replace(/\[Object: null prototype\] /g, '')
}
})
if (Object.keys(context).length) Object.assign (repl.context, context)
cds.extend (repl.context) .with ( class {
get Foo() { return ctx.Foo ??= new cds.entity({ name: 'Foo' }) }; set Foo(v) { ctx.Foo = v }
get Bar() { return ctx.Bar ??= new cds.entity({ name: 'Bar' }) }; set Bar(v) { ctx.Bar = v }
get expect() { return ctx.expect ??= cds.test.expect }; set expect(v) { ctx.expect = v }
} .prototype )
const ctx={}
repl.defineCommand ('run', { action: _run,
help: 'Runs a cds server from a given CAP project folder, or module name like @capire/bookshop.',
})
repl.defineCommand ('inspect', { action: _inspect,
help: 'Sets options for util.inspect, e.g. `.inspect .depth=1`.',
})
repl.history = new Array(222)
const home = process.env.HOME || process.env.USERPROFILE
const history = require('path').join(home,'.cds-repl-history')
const fs = require('fs')
fs.readFile(history, 'utf-8', (e, txt) => e || Object.assign(repl.history, txt.split('\n')))
repl.on('exit', () => fs.writeFile(history, repl.history.join('\n'), () => {}))
repl.on('exit', cds.shutdown)
process.on('uncaughtException', console.error)
process.on('unhandledRejection', console.error)
// Ugly hack to prevent "[ERR_INVALID_REPL_INPUT]: Listeners for `uncaughtException` cannot be used in the REPL" errors caused by winston when connecting to remote services
process.on = (event, listener) => event !== 'uncaughtException' && event !== 'unhandledRejection' && process.addListener(event, listener)
async function _run (project) {
await cds.test (project) // await cds.exec ('--project', project, '--port', '0')
_export ('entities', 'services')
}
function _export (...cds_features) {
console.log()
console.log ('------------------------------------------------------------------------')
console.log ('Following variables are made available in your repl\'s global context:')
console.log()
for (let each of cds_features) {
let module = cds[each]
console.log (`from cds.${each}: {`)
for (let p in module) {
if (p in global || p.startsWith('_') || p.includes('.')) continue
console.log (` ${GREEN}${p}${RESET},`)
repl.context[p] = module [p]
}
console.log (`}`)
console.log()
}
console.log('Simply type e.g.', GREEN+ Object.keys(repl.context).at(-1) +RESET, 'in the prompt to use the respective objects.')
console.log()
repl.displayPrompt()
}
async function _inspect (_args) {
const args = _args.split(' '), subjects={}, options = {}, defaults = inspect.defaultOptions
for (let each of args) {
// args of shape .<option>=<value> set inspect options
if (each[0] === '.') each = eval(`options${each}`)
// others are subjects to inpsect with given options
else subjects[each ||= 'defaults'] = each === 'defaults' ? defaults : function _recurse (o,slug) {
const [ head,...tail ] = slug, x = o[head]
if (tail.length && x && typeof x === 'object') return _recurse (x,tail)
else return x
} (repl.context, each.split('.'))
}
// if no subjects were given, just set the options globally
if (!Object.keys(subjects).length) {
Object.assign (defaults, options)
console.log('\n', 'updated node:util.inspect.defaultOptions with:', options)
} else for (let [each,subject] of Object.entries(subjects))
console.log ('\n'+each+':', inspect (subject, options))
console.log()
repl.displayPrompt()
}
}
if (!module.parent) repl()