UNPKG

draig-car

Version:

Database REST API interactive generator CLI and REPL OpenAPI3 based JS generator with interactive ORM/ODM REPL

231 lines (218 loc) 6.24 kB
const fs = require('fs') const util = require('util') const chalk = require('chalk') const mustache = require('mustache') const u = require('../util') let workSchema = {} function getParsedTemplate(tpl) { const parsed = mustache.parse(tpl) const r = e => e[0] === 'name' || e[0] === '&' ? e[1] : e[0] === '#' ? { [e[1]]: e[4].reduce((i, e) => i.concat(r(e)), []) } : e[0] === '^' ? { ['not' + e[1]]: e[4].reduce((i, e) => i.concat(r(e)), []) } : [] return parsed.reduce((i, e) => i.concat(r(e)), []) } const completers = api => ({ yesno: ['yes', 'no'], path: Object.keys(api.paths), tag: api.tags.map(e => e.name), schema: Object.keys(api.components.schemas), bodySchema: Object.keys(api.components.schemas), schemaPart: Object.keys(api.components.schemas), columnType: ['integer', 'string', 'number', 'boolean'], refType: Object.keys(api.components.schemas), required: 'properties' in workSchema ? Object.keys(workSchema.properties) : 'allOf' in workSchema && workSchema.allOf.find(o => 'properties' in o) ? Object.keys(workSchema.allOf.find(o => 'properties' in o).properties) : [], format: [ 'int32', 'int64', 'float', 'double', 'byte', 'binary', 'date', 'date-time', 'password', 'email', 'uuid' ] }) function completer(repl, bar) { return (line, callback) => { let comp = util.isArray(bar) ? bar : completers(repl.context.api)[bar] || [] const hits = comp.filter(c => c.startsWith(line)) const ret = [hits && hits.length ? hits : comp, line || ''] callback(null, ret) } } async function askVarObjects(repl, varName, vlist) { let values = {} for (let v of vlist) await getVars(repl, values, v) return values } function question(repl, completions, msg, defvalue) { return new Promise((resolve, reject) => { let _comp = repl.completer repl.completer = completer(repl, completions) if(defvalue) repl.history.unshift(defvalue) repl.question(msg, r => { repl.completer = _comp if (r) repl.history.splice(0, 1) resolve(r) }) }) } async function askVarList(repl, varName, vlist) { let valuesList = [] while (true) { let values = {} for (let v of vlist) await getVars(repl, values, v) if (Object.keys(values).length) valuesList.push(values) else break } return valuesList } async function getVars(repl, values, v) { let varName = util.isObject(v) ? Object.keys(v)[0] : v if (values[varName]) return if (varName.endsWith('List')) { values[varName] = await askVarList(repl, varName, v[varName]) } else if (varName.startsWith('has')) { if ( 'yes' === (await question( repl, 'yesno', chalk`Has this {green ${varName.replace('has', '')}}: ` )) ) values[varName] = await askVarObjects(repl, varName, v[varName]) } else if (varName.startsWith('not')) { let value = values[varName.replace('not', '')] if (value === undefined) for (let e of v[varName]) await getVars(repl, values, e) } else { let resp, opt = util.isObject(v) ? 'optional' : 'required' resp = await question( repl, varName, chalk` {green ?} Value for {green ${varName}} (${opt}): ` ) if (resp) values[varName] = resp } } function allLocalSchemas(schemas, view) { let local = /#\/components\/schemas\// if (view) for (let [k, v] of Object.entries(view)) { if ('object' === typeof v) allLocalSchemas(schemas, v) else if (k === '$ref' && !schemas.includes(v.replace(local, ''))) schemas.push(v.replace(local, '')) } return schemas } async function askVars(repl, name, tpl) { let values = {} console.log(chalk`Please enter values for the {green ${name}} template:\n`) for (let v of getParsedTemplate(tpl)) await getVars(repl, values, v) return values } module.exports = { question, askVars, apiGet: function(obj, path) { return path.length ? obj[path[0]] && module.exports.apiGet(obj[path[0]], path.slice(1)) : obj }, apiSet: function(obj, path, value) { if(path.length > 1) { if(obj[path[0]]) return module.exports.apiSet(obj[path.shift()], path, value) } else { obj[path[0]] = value return true } return false }, apiValue: function(ctx, args) { let cmdline = util.isArray(args) ? args : args.split(' ') let path = cmdline[0] === '' ? ctx.apiPath : cmdline[0] === 'api' ? cmdline.slice(1) : ctx.apiPath.concat(cmdline) let value = ctx.api for (let p of path) { if (typeof value[p] === 'undefined') return [undefined] value = value[p] } return [value, path.reduce((i, e) => i + "['" + e + "']", ''), path] }, fillTemplate: async function(repl, tplDir, tplName, wsch) { if (wsch) workSchema = wsch // read template let tpl = u.template(repl.context.argv, tplDir, tplName) if (!tpl) { console.error( chalk`Template {yellow ${tplDir}/${tplName}} not found or unreadable` ) return null } // ask for vars of the template let values = await askVars(repl, tplName, tpl) // make mustache substitution let yamlSubst = mustache.render(tpl, values) // ask for confirmation showing prettyfied yaml substitution u.printYaml(yamlSubst) let confirmed = await question(repl, 'yesno', 'Is the above data OK? ') if (!confirmed || confirmed === 'no') { console.log(chalk`{yellow Aborted} - No API modification performed`) return null } // return view from yaml return u.fromYaml(yamlSubst) }, addView: function(obj, view) { let key = Object.keys(view)[0] return key ? { ...obj, [key]: { ...obj[key], ...view[key] } } : obj }, validOperationId: function(paths, operationId) { let op = Object.values(paths).find(p => Object.values(p).find(o => o.operationId === operationId) ) if (op) console.error( chalk`{yellow ERROR} - operationId {yellow ${operationId}} already exists` ) return typeof op === 'undefined' }, validSchemas: function(api, view) { let schemas = allLocalSchemas([], view) let missing = schemas.filter( s => !api.components.schemas.hasOwnProperty(s) ) if (missing.length) { console.error( chalk`{yellow ERROR} - Found references to missing or non-local schemas:`, missing ) console.error( 'Generation will CRASH if the problem persist !!!' ) } return missing.length === 0 } }