UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

144 lines (117 loc) 5.39 kB
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()