Closed github-actions[bot] closed 4 years ago
use-plugin needs better error message for malformed plugin desc
https://github.com/senecajs/seneca/blob/1c97c5fba6f4e3c61b037be42656036a83321b36/lib/plugin.js#L118
Object.defineProperty(exports, "__esModule", { value: true }); const Uniq = require('lodash.uniq'); const Eraro = require('eraro'); const nua_1 = __importDefault(require("nua")); const ordu_1 = require("ordu"); // TODO: refactor: use.js->plugin.js and contain *_plugin api methods too const Common = require('./common'); const Print = require('./print'); /* $lab:coverage:on$ */ exports.api_use = api_use; const intern = exports.intern = make_intern(); function api_use(callpoint, opts) { const tasks = make_tasks(); const ordu = new ordu_1.Ordu({ debug: opts.debug }); ordu.operator('seneca_plugin', intern.op.seneca_plugin); ordu.operator('seneca_export', intern.op.seneca_export); ordu.operator('seneca_options', intern.op.seneca_options); ordu.operator('seneca_complete', intern.op.seneca_complete); // TODO: exports -> meta and handle all meta processing ordu.add([ tasks.args, tasks.load, tasks.normalize, tasks.preload, { name: 'pre_meta', exec: tasks.meta }, { name: 'pre_legacy_extend', exec: tasks.legacy_extend }, tasks.delegate, tasks.call_define, tasks.options, tasks.define, { name: 'post_meta', exec: tasks.meta }, { name: 'post_legacy_extend', exec: tasks.legacy_extend }, tasks.call_prepare, tasks.complete, ]); return { use: make_use(ordu, callpoint), ordu, tasks, }; } function make_use(ordu, callpoint) { let seq = { index: 0 }; return function use() { let self = this; let args = [...arguments]; if (0 === args.length) { throw self.error('use_no_args'); } let ctx = { seq: seq, args: args, seneca: this, callpoint: callpoint(true) }; let data = { seq: -1, args: [], plugin: null, meta: null, delegate: null, plugin_done: null, exports: {}, prepare: {}, }; async function run() { await ordu.exec(ctx, data, { done: function (res) { if (res.err) { var err = res.err.seneca ? res.err : self.private$.error(res.err, res.err.code); self.die(err); } } }); } // NOTE: don't wait for result! run(); return self; }; } function make_tasks() { return { // TODO: args validation? args: (spec) => { let args = [...spec.ctx.args]; // DEPRECATED: Remove when Seneca >= 4.x // Allow chaining with seneca.use('options', {...}) // see https://github.com/rjrodger/seneca/issues/80 if ('options' === args[0]) { spec.ctx.seneca.options(args[1]); return { op: 'stop', why: 'legacy-options' }; } // Plugin definition function is under property `define`. // `init` is deprecated from 4.x // TODO: use-plugin expects `init` - update use-plugin to make this customizable if (null != args[0] && 'object' === typeof args[0]) { args[0].init = args[0].define || args[0].init; } return { op: 'merge', out: { plugin: { args } } }; }, load: (spec) => { let args = spec.data.plugin.args; let seneca = spec.ctx.seneca; let private$ = seneca.private$; // TODO: use-plugin needs better error message for malformed plugin desc let desc = private$.use.build_plugin_desc(...args); if (private$.ignore_plugins[desc.full]) { seneca.log.info({ kind: 'plugin', case: 'ignore', plugin_full: desc.full, plugin_name: desc.name, plugin_tag: desc.tag, }); return { op: 'stop', why: 'ignore' }; } else { let plugin = private$.use.use_plugin_desc(desc); return { op: 'merge', out: { plugin } }; } }, normalize: (spec) => { let plugin = spec.data.plugin; let modify = {}; // NOTE: `define` is the property for the plugin definition action. // The property `init` will be deprecated in 4.x modify.define = plugin.define || plugin.init; modify.fullname = Common.make_plugin_key(plugin); modify.loading = true; return { op: 'merge', out: { plugin: modify } }; }, preload: (spec) => { let seneca = spec.ctx.seneca; let plugin = spec.data.plugin; let so = seneca.options(); // Don't reload plugins if load_once true. if (so.system.plugin.load_once) { if (seneca.has_plugin(plugin)) { return { op: 'stop', why: 'already-loaded', out: { plugin: { loading: false } } }; } } let meta = {}; if ('function' === typeof plugin.define.preload) { // TODO: need to capture errors meta = plugin.define.preload.call(seneca, plugin) || {}; } let name = meta.name || plugin.name; let fullname = Common.make_plugin_key(name, plugin.tag); return { op: 'seneca_plugin', out: { merge: { meta, plugin: { name, fullname } }, plugin } }; }, meta: (spec) => { let seneca = spec.ctx.seneca; let plugin = spec.data.plugin; let meta = spec.data.meta; let exports = {}; exports[plugin.name] = meta.export || plugin; exports[plugin.fullname] = meta.export || plugin; let exportmap = meta.exportmap || meta.exports || {}; Object.keys(exportmap).forEach(k => { let v = exportmap[k]; if (void 0 !== v) { let exportname = plugin.fullname + '/' + k; exports[exportname] = v; } }); if (meta.order) { if (meta.order.plugin) { let tasks = Array.isArray(meta.order.plugin) ? meta.order.plugin : [meta.order.plugin]; //console.log('AAA', spec.task.name, tasks) //try { seneca.order.plugin.add(tasks); //} //catch (e) { // console.log(e) //} delete meta.order.plugin; } } return { op: 'seneca_export', out: { exports } }; }, // NOTE: mutates spec.ctx.seneca legacy_extend: (spec) => { let seneca = spec.ctx.seneca; // let plugin: any = spec.data.plugin let meta = spec.data.meta; if ('object' === typeof meta.extend) { if ('function' === typeof meta.extend.action_modifier) { seneca.private$.action_modifiers.push(meta.extend.action_modifier); } // FIX: needs to use logging.load_logger if ('function' === typeof meta.extend.logger) { if (!meta.extend.logger.replace && 'function' === typeof seneca.private$.logger.add) { seneca.private$.logger.add(meta.extend.logger); } else { seneca.private$.logger = meta.extend.logger; } } } //seneca.register(plugin, meta) }, delegate: (spec) => { let seneca = spec.ctx.seneca; let plugin = spec.data.plugin; // Adjust Seneca API to be plugin specific. let delegate = seneca.delegate({ plugin$: { name: plugin.name, tag: plugin.tag, }, fatal$: true, }); delegate.private$ = Object.create(seneca.private$); delegate.private$.ge = delegate.private$.ge.gate(); delegate.die = Common.makedie(delegate, { type: 'plugin', plugin: plugin.name, }); let actdeflist = []; delegate.add = function () { let argsarr = [...arguments]; let actdef = argsarr[argsarr.length - 1] || {}; if ('function' === typeof actdef) { actdef = {}; argsarr.push(actdef); } actdef.plugin_name = plugin.name || '-'; actdef.plugin_tag = plugin.tag || '-'; actdef.plugin_fullname = plugin.fullname; // TODO: is this necessary? actdef.log = delegate.log; actdeflist.push(actdef); seneca.add.apply(delegate, argsarr); // FIX: should be this return delegate; }; delegate.__update_plugin__ = function (plugin) { delegate.context.name = plugin.name || '-'; delegate.context.tag = plugin.tag || '-'; delegate.context.full = plugin.fullname || '-'; actdeflist.forEach(function (actdef) { actdef.plugin_name = plugin.name || actdef.plugin_name || '-'; actdef.plugin_tag = plugin.tag || actdef.plugin_tag || '-'; actdef.plugin_fullname = plugin.fullname || actdef.plugin_fullname || '-'; }); }; delegate.init = function (init) { // TODO: validate init_action is function let pat = { role: 'seneca', plugin: 'init', init: plugin.name, }; if (null != plugin.tag && '-' != plugin.tag) { pat.tag = plugin.tag; } delegate.add(pat, function (_, reply) { init.call(this, reply); }); }; delegate.context.plugin = plugin; delegate.context.plugin.mark = Math.random(); return { op: 'merge', out: { delegate } }; }, call_define: (spec) => { let plugin = spec.data.plugin; let delegate = spec.data.delegate; // FIX: mutating context!!! let seq = spec.ctx.seq.index++; let plugin_define_pattern = { role: 'seneca', plugin: 'define', name: plugin.name, seq: seq, }; if (plugin.tag !== null) { plugin_define_pattern.tag = plugin.tag; } return new Promise(resolve => { // seneca delegate.add(plugin_define_pattern, (_, reply) => { resolve({ op: 'merge', out: { seq, plugin_done: reply } }); }); delegate.act({ role: 'seneca', plugin: 'define', name: plugin.name, tag: plugin.tag, seq: seq, default$: {}, fatal$: true, local$: true, }); }); }, options: (spec) => { let plugin = spec.data.plugin; let delegate = spec.data.delegate; let so = delegate.options(); let fullname = plugin.fullname; let defaults = plugin.defaults || {}; let fullname_options = Object.assign({}, // DEPRECATED: remove in 4 so[fullname], so.plugin[fullname], // DEPRECATED: remove in 4 so[fullname + '$' + plugin.tag], so.plugin[fullname + '$' + plugin.tag]); let shortname = fullname !== plugin.name ? plugin.name : null; if (!shortname && fullname.indexOf('seneca-') === 0) { shortname = fullname.substring('seneca-'.length); } let shortname_options = Object.assign({}, // DEPRECATED: remove in 4 so[shortname], so.plugin[shortname], // DEPRECATED: remove in 4 so[shortname + '$' + plugin.tag], so.plugin[shortname + '$' + plugin.tag]); let base = {}; // NOTE: plugin error codes are in their own namespaces // TODO: test this!!! let errors = plugin.errors || (plugin.define && plugin.define.errors); if (errors) { base.errors = errors; } let outopts = Object.assign(base, shortname_options, fullname_options, plugin.options || {}); let resolved_options = {}; //console.log('oAAA', delegate.util.Joi.isSchema(defaults)) // TODO: expose this on plugin let joi_schema = intern.prepare_spec(delegate.util.Joi, defaults, { allow_unknown: true }, {}); //console.log('oBBB', joi_schema === defaults) let joi_out = joi_schema.validate(outopts); //console.log('oCCC', joi_out) let err = void 0; if (joi_out.error) { err = delegate.error('invalid_plugin_option', { name: fullname, err_msg: joi_out.error.message, options: outopts, }); //console.log('oDDD', err) } else { resolved_options = joi_out.value; } return { op: 'seneca_options', err: err, out: { plugin: { options: resolved_options, options_schema: joi_schema } } }; }, // TODO: move data modification to returned operation define: (spec) => { let seneca = spec.ctx.seneca; let plugin = spec.data.plugin; let delegate = spec.data.delegate; let plugin_options = spec.data.plugin.options; delegate.log.debug({ kind: 'plugin', case: 'DEFINE', name: plugin.name, tag: plugin.tag, options: plugin_options, callpoint: spec.ctx.callpoint, }); let meta = intern.define_plugin(delegate, plugin, seneca.util.clean(plugin_options)); plugin.meta = meta; // legacy api for service function if ('function' === typeof meta) { meta = { service: meta }; } // Plugin may have changed its own name dynamically plugin.name = meta.name || plugin.name; plugin.tag = meta.tag || plugin.tag || (plugin.options && plugin.options.tag$); plugin.fullname = Common.make_plugin_key(plugin); plugin.service = meta.service || plugin.service; delegate.__update_plugin__(plugin); seneca.private$.plugins[plugin.fullname] = plugin; seneca.private$.plugin_order.byname.push(plugin.name); seneca.private$.plugin_order.byname = Uniq(seneca.private$.plugin_order.byname); seneca.private$.plugin_order.byref.push(plugin.fullname); // 3.x Backwards compatibility - REMOVE in 4.x if ('amqp-transport' === plugin.name) { seneca.options({ legacy: { meta: true } }); } if ('function' === typeof plugin_options.defined$) { plugin_options.defined$(plugin); } // TODO: test this, with preload, explicitly return { op: 'merge', out: { meta, } }; }, call_prepare: (spec) => { let plugin = spec.data.plugin; let plugin_options = spec.data.plugin.options; let delegate = spec.data.delegate; // If init$ option false, do not execute init action. if (false === plugin_options.init$) { return; } let exports = spec.data.exports; delegate.log.debug({ kind: 'plugin', case: 'INIT', name: plugin.name, tag: plugin.tag, exports: exports, }); return new Promise(resolve => { delegate.act({ role: 'seneca', plugin: 'init', seq: spec.data.seq, init: plugin.name, tag: plugin.tag, default$: {}, fatal$: true, local$: true, }, function (err, res) { resolve({ op: 'merge', out: { prepare: { err, res } } }); }); }); }, complete: (spec) => { let prepare = spec.data.prepare; let plugin = spec.data.plugin; let plugin_done = spec.data.plugin_done; let plugin_options = spec.data.plugin.options; let delegate = spec.data.delegate; let so = delegate.options(); if (prepare) { if (prepare.err) { let plugin_out = {}; plugin_out.err_code = 'plugin_init'; plugin_out.plugin_error = prepare.err.message; if (prepare.err.code === 'action-timeout') { plugin_out.err_code = 'plugin_init_timeout'; plugin_out.timeout = so.timeout; } return { op: 'seneca_complete', out: { plugin: plugin_out } }; } let fullname = plugin.name + (plugin.tag ? '$' + plugin.tag : ''); if (so.debug.print && so.debug.print.options) { Print.plugin_options(delegate, fullname, plugin_options); } delegate.log.info({ kind: 'plugin', case: 'READY', name: plugin.name, tag: plugin.tag, }); if ('function' === typeof plugin_options.inited$) { plugin_options.inited$(plugin); } } plugin_done(); return { op: 'seneca_complete', out: { plugin: { loading: false } } }; } }; } function make_intern() { return { // TODO: explicit tests for these operators op: { seneca_plugin: (tr, ctx, data) => { nua_1.default(data, tr.out.merge, { preserve: true }); ctx.seneca.private$.plugins[data.plugin.fullname] = tr.out.plugin; return { stop: false }; }, seneca_export: (tr, ctx, data) => { Object.assign(data.exports, tr.out.exports); Object.assign(ctx.seneca.private$.exports, tr.out.exports); return { stop: false }; }, seneca_options: (tr, ctx, data) => { nua_1.default(data.plugin, tr.out.plugin, { preserve: true }); let plugin_fullname = data.plugin.fullname; let plugin_options = data.plugin.options; let plugin_options_update = { plugin: {} }; plugin_options_update.plugin[plugin_fullname] = plugin_options; ctx.seneca.options(plugin_options_update); return { stop: false }; }, seneca_complete: (tr, _ctx, data) => { nua_1.default(data.plugin, tr.out.plugin, { preserve: true }); if (data.prepare.err) { data.delegate.die(data.delegate.error(data.prepare.err, data.plugin.err_code, data.plugin)); } return { stop: true }; }, }, define_plugin: function (delegate, plugin, options) { // legacy plugins if (plugin.define.length > 1) { let fnstr = plugin.define.toString(); plugin.init_func_sig = (fnstr.match(/^(.*)\r*\n/) || [])[1]; let ex = delegate.error('unsupported_legacy_plugin', plugin); throw ex; } if (options.errors) { plugin.eraro = Eraro({ package: 'seneca', msgmap: options.errors, override: true, }); } let meta; try { meta = plugin.define.call(delegate, options) || {}; } catch (e) { Common.wrap_error(e, 'plugin_define_failed', { fullname: plugin.fullname, message: (e.message + (' (' + e.stack.match(/\n.*?\n/)).replace(/\n.*\//g, '')).replace(/\n/g, ''), options: options, repo: plugin.repo ? ' ' + plugin.repo + '/issues' : '', }); } meta = 'string' === typeof meta ? { name: meta } : meta; meta.options = meta.options || options; let updated_options = {}; updated_options[plugin.fullname] = meta.options; delegate.options(updated_options); return meta; }, // copied from https://github.com/rjrodger/optioner // TODO: remove unnecessary vars+code prepare_spec: function (Joi, spec, opts, ctxt) { if (Joi.isSchema(spec)) { return spec; } let joiobj = Joi.object(); if (opts.allow_unknown) { joiobj = joiobj.unknown(); } let joi = intern.walk(Joi, joiobj, spec, '', opts, ctxt, function (valspec) { if (valspec && Joi.isSchema(valspec)) { return valspec; } else { let typecheck = typeof valspec; //typecheck = 'function' === typecheck ? 'func' : typecheck if (opts.must_match_literals) { return Joi.any() .required() .valid(valspec); } else { if (void 0 === valspec) { return Joi.any().optional(); } else if (null == valspec) { return Joi.any().default(null); } else if ('number' === typecheck && Number.isInteger(valspec)) { return Joi.number() .integer() .default(valspec); } else if ('string' === typecheck) { return Joi.string() .empty('') .default(() => valspec); } else { return Joi[typecheck]().default(() => valspec); } } } }); return joi; }, // copied from https://github.com/rjrodger/optioner // TODO: remove unnecessary vars+code walk: function (Joi, start_joiobj, obj, path, opts, ctxt, mod) { let joiobj = start_joiobj; // NOTE: use explicit Joi construction for checking within arrays if (Array.isArray(obj)) { return Joi.array(); } else { for (let p in obj) { let v = obj[p]; let t = typeof v; let kv = {}; if (null != v && !Joi.isSchema(v) && 'object' === t) { let np = '' === path ? p : path + '.' + p; let childjoiobj = Joi.object().default(); if (opts.allow_unknown) { childjoiobj = childjoiobj.unknown(); } kv[p] = intern.walk(Joi, childjoiobj, v, np, opts, ctxt, mod); } else { kv[p] = mod(v); } joiobj = joiobj.keys(kv); } return joiobj; } } }; } //# sourceMappingURL=plugin.js.map No newline at end of file ew file mode 100644 ndex 00000000..c607f7e2 ++ b/lib/plugin.js.map
Closed in 1c97c5fba6f4e3c61b037be42656036a83321b36
use-plugin needs better error message for malformed plugin desc
https://github.com/senecajs/seneca/blob/1c97c5fba6f4e3c61b037be42656036a83321b36/lib/plugin.js#L118