UNPKG

@sap/cds-dk

Version:

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

227 lines (183 loc) 7.02 kB
#! /usr/bin/env node /* USAGE: cds inspect model.Req cds inspect model.Requisition --ids */ module.exports = Object.assign (inspect, { options: [ '--stats', '--list', '--kind' ], shortcuts: [ '-s', '-l', '-k' ], help: ` # SYNOPSIS *cds inspect* [ <options> ] Inspects the cds models of a project, or a specific definition. Without arguments shows --stats a summary of your models. You can list all definitions, and thereby filter the listed definitions by kind, and/or a name pattern, using wildcards like * and ?. For a specific definition you can list its elements, keys, associations, compositions, or IDs using --list. It also shows where the definition is inherited from, and counts of its elements. See examples below. # OPTIONS *-s | --stats* Shows a summary of the kinds of definitions found. *-l | --list* <names|elements|keys|associations|compositions|ids> Lists the specified kind of elements of a specific definition. *-k | --kind* <entity|type|service|...> Filters the listed definitions by kind. # EXAMPLES *cds inspect* *cds inspect* --stats *cds inspect* --kind aspect *cds inspect* --list all *cds inspect* --list names --kind entity *cds inspect* --list names model.Req* *cds inspect* requisition.model.Requisition *cds inspect* requisition.model.Requisition --list elements *cds inspect* requisition.model.Requisition --list ids *cds inspect* requisition.model.Requisition --list keys *cds inspect* requisition.model.Requisition --list associations *cds inspect* requisition.model.Requisition --list compositions `}) async function inspect(){ const cds = require ('../lib/cds') const path = require ('node:path') const { YELLOW, GREEN, CYAN, RED, GRAY, RESET } = cds.utils.colors const i2 = ' '.repeat(2) const i4 = ' '.repeat(4) const args = process.argv.slice(3) const option = (...oo) => { for (let o of oo) { const i = args.indexOf(o) if (i>=0) return args.splice(i,2)[1] || '' } } const fetch = { __proto__: { get 'id elements' () { return this.ids }, }, names: () => true, elements: () => true, ids: e => e.name.match(/(ID|Id|id$)/), keys: e => e.key, associations: a => a.isAssociation && !a.isComposition, compositions: a => a.isComposition, 'to-many': a => a.is2many, 'to-one': a => a.is2one, unmanaged: a => a.is2one && a.on, virtuals: e => e.virtual, calculated: e => e.value, } const $location = d => { if (Object.hasOwn(d,'$location')) return d.$location.file+':'+d.$location.line let includes = d.parent?.includes; if (!includes) return '' for (let inc of includes) { let e = csn.definitions[inc]?.elements?.[d.name] if (e) return $location(e) } } const csn = await cds.load('*').then(cds.linked) let defs = Object.values (csn.definitions) const kinds = { entities: 'entity', aspects: 'aspect', types: 'type', services: 'service', events: 'event', actions: 'action', functions: 'function', contexts: 'context', all: undefined, } let kind = option ('-k','--kind') let list = option ('-l','--list') let stats = option ('-s','--stats') if (list in kinds) [kind,list] = [kinds[list],'names'] if (kind) defs = defs.filter (d => d.kind === kind) if (process.argv.length < 4) stats = true const regexp4 = pattern => RegExp (pattern.replace(/\./g, '\\.').replace(/\*/g, '.*')) for (let pattern of args) { const regexp = regexp4(pattern || '.*') defs = defs.filter (d => regexp.test(d.name)) } console.log() if (stats !== undefined) { const count = (kind,f) => { let n = defs.filter(f).length; if (!n) return if (kind === 'entities') for (let each of ['.texts','.drafts']) { let n = defs.filter(d => d.name.endsWith(each)).length if (n) kind += ` - ${YELLOW}${n}${GRAY} ${each}` } if (n) console.log (YELLOW, n.toString().padStart(4,' '), GRAY + kind) } for (let k in kinds) count (k, d => d.kind === kinds[k]) console.log (GRAY, '-'.repeat(14), RESET) console.log (YELLOW, defs.length.toString().padStart(4,' '), GRAY + 'in total') } else { defs = defs.filter (d => !d.name.match(/\.(texts|drafts)$/)) switch (list) { case 'names': const pad = kind?.length || 7 // eslint-disable-line no-case-declarations defs.forEach (d => console.log (i2, RED + d.kind.padEnd(pad), GREEN + d.name)) console.log(GRAY+i2, '-'.repeat(27), RESET) console.log(GRAY+i2, 'Total:', YELLOW + defs.length, GRAY + 'definitions') break; case 'sources': csn.$sources.forEach(f => console.log (GRAY+i2, path.relative(cds.root,f))) break; default: defs.forEach (inspect) } } console.log(RESET) function inspect (d) { console.log(GRAY+i2, '-'.repeat(79), RESET) console.log(GRAY+i2, 'Inspecting', RED + d.kind, GREEN + d.name, RESET) console.log(GRAY+i2, '( declared at'+ CYAN, $location(d), GRAY+')', RESET) console.log() if (list !== undefined) { const filter = fetch [list] if (!filter) return console.error (RED, 'ERROR:'+ RESET, 'Use --list with one of:\n\n ', Object.keys(fetch).join('\n '), RESET) const elements = Object.values(d.elements??{}).filter(filter) const pad = elements.reduce((p, e) => Math.max(p, e.name.length), 0) for (let e of elements) { console.log (i4, GRAY + '-', YELLOW + e.name.padEnd(pad), GRAY, $location(e)) } return console.log() } count(d.elements, 'keys') count(d.elements, 'id elements') count(d.elements, 'elements') count(d.elements, 'associations', 'to-many', 'unmanaged') count(d.elements, 'compositions', 'to-many') count(d.elements, 'virtuals') count(d.elements, 'calculated') if (d.includes) { console.log(RESET + '\n', i2, 'inherits: \n') inheritance(d) } console.log() } function inheritance (d, indent = '') { if (d.includes) for (let each of d.includes) { let d = csn.definitions[each] console.log(i4, ' -', count(d.elements), GRAY + 'elements from' + indent, RED + d.kind, GREEN + d.name, RESET) inheritance(d, indent + i2) } } function count (defs, kind, ...more) { if (!defs) return if (!Array.isArray(defs)) defs = Object.values(defs) if (kind) { const filter = fetch[kind] if (filter) defs = defs.filter(filter) if (!defs.length) return } let info = YELLOW+ defs.length.toString().padStart(3, ' ') if (kind) info += GRAY + ' ' + kind; else return info for (let kind of more) { let filter = fetch[kind] || fetch[kind.replace(/-/g,'_')] let n = defs.filter(filter).length if (n) info += ', '+ YELLOW + defs.filter(filter).length + GRAY+' ' + kind } console.log (i4, info, GRAY) } }