bajo
Version:
The ultimate framework for whipping up massive apps in no time
209 lines (200 loc) • 6.58 kB
JavaScript
import semver from 'semver'
import lodash from 'lodash'
import Print from '../plugin/print.js'
const {
merge,
forOwn,
groupBy,
find,
reduce,
map,
trim,
keys,
intersection,
each,
camelCase,
get
} = lodash
/**
* Internal helpers called by Bajo & plugins that only used once for bootstrapping purpose.
* It should remains hidden and not to be imported by any program.
*
* @module Helper/Base
*/
/**
* Build configurations
*
* @async
*/
export async function buildConfigs () {
this.bajo.log.debug('readConfigs')
for (const ns of this.getAllNs()) {
await this[ns].loadConfig()
this[ns].print = new Print(this[ns])
this.loadIntl(ns)
}
}
/**
* Ensure for names and aliases to be unique and no clashes with other plugins
*
* @async
*/
export async function checkNameAliases () {
this.bajo.log.debug('checkAliasNameClash')
const refs = []
for (const pkg of this.bajo.app.pluginPkgs) {
const plugin = this.bajo.app[camelCase(pkg)]
const { ns, alias } = plugin
let item = find(refs, { ns })
if (item) throw this.bajo.error('pluginNameClash%s%s%s%s', ns, pkg, item.ns, item.pkg, { code: 'BAJO_NAME_CLASH' })
item = find(refs, { alias })
if (item) throw this.bajo.error('pluginNameClash%s%s%s%s', alias, pkg, item.alias, item.pkg, { code: 'BAJO_ALIAS_CLASH' })
refs.push({ ns, alias, pkg })
}
}
/**
* Ensure dependencies are met
*
* @async
*/
export async function checkDependencies () {
const { join } = this.bajo
this.bajo.log.debug('checkDeps')
for (const pkg of this.bajo.app.pluginPkgs) {
const plugin = this.bajo.app[camelCase(pkg)]
const { ns, dependencies } = plugin
this.bajo.log.trace('- %s', ns)
const odep = reduce(dependencies, (o, k) => {
const item = map(k.split('@'), m => trim(m))
if (k[0] === '@') o['@' + item[1]] = item[2]
else o[item[0]] = item[1]
return o
}, {})
const deps = keys(odep)
if (deps.length > 0) {
if (intersection(this.bajo.app.pluginPkgs, deps).length !== deps.length) {
throw this.bajo.error('dependencyUnfulfilled%s%s', pkg, join(deps), { code: 'BAJO_DEPENDENCY' })
}
each(deps, d => {
if (!odep[d]) return
const ver = get(this.bajo.app[camelCase(d)], 'pkg.version')
if (!ver) return
if (!semver.satisfies(ver, odep[d])) {
throw this.bajo.error('semverCheckFailed%s%s', pkg, `${d}@${odep[d]}`, { code: 'BAJO_DEPENDENCY_SEMVER' })
}
})
}
}
}
/**
* Collect and build hooks and push them to the bajo's hook system
*
* @async
* @fires bajo:afterCollectHooks
*/
export async function collectHooks () {
const { eachPlugins, runHook, isLogInRange, importModule } = this.bajo
const me = this // "this" is "app"
me.bajo.log.trace('collecting%s', this.t('hooks'))
await eachPlugins(async function ({ dir, file }) {
const mod = await importModule(file, { asHandler: true })
if (!mod) return undefined
const _file = file.replace(dir + '/hook/', '').replace('.js', '')
let [names, path] = _file.split('@')
names = names.split('$').map(n => trim(n))
for (const name of names) {
let [ns, subNs, subSubNs] = name.split('.').map(n => camelCase(n))
if (subSubNs) subNs = `${subNs}.${subSubNs}`
const m = merge({}, mod, { ns, subNs, path: camelCase(path), src: this.ns })
me.bajo.hooks.push(m)
}
}, { glob: 'hook/**/*.js', prefix: me.bajo.ns })
// for log trace purpose only
if (isLogInRange('trace')) {
const items = groupBy(me.bajo.hooks, item => item.ns + (item.subNs ? `.${item.subNs}` : ''))
forOwn(items, (v, k) => {
const hooks = groupBy(v, 'path')
forOwn(hooks, (v1, k1) => {
me.bajo.log.trace('- %s:%s (%d)', k, k1, v1.length)
})
})
}
/**
* Run after hooks are collected
*
* @global
* @event bajo:afterCollectHooks
* @param {Object[]} hooks - Array of hook objects
* @see {@tutorial hook}
* @see module:Helper/Base.collectHooks
*/
await runHook('bajo:afterCollectHooks', this.bajo.hooks)
me.bajo.log.debug('collected%s%d', this.t('hooks'), me.bajo.hooks.length)
}
/**
* Finally, run all plugins
*
* @async
* @fires bajo:beforeAll{method}
* @fires {ns}:before{method}
* @fires {ns}:after{method}
* @fires bajo:afterAll{method}
*/
export async function run () {
const me = this
const { runHook, eachPlugins, join } = me.bajo
const { freeze } = me.lib
const methods = ['init']
if (!me.applet) methods.push('start')
for (const method of methods) {
/**
* Run before all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
*
* @global
* @event bajo:beforeAll{method}
* @param {string} method - Accepted methods: ```Init```, ```Start```
* @see module:Helper/Base.run
*/
await runHook(`bajo:${camelCase(`before all ${method}`)}`)
await eachPlugins(async function () {
const { ns } = this
/**
* Run before ```{method}``` is executed within ```{ns}``` context
*
* - ```{ns}``` - namespace
* - ```{method}``` - Accepted methods: ```Init``` or ```Start```
*
* @global
* @event {ns}:before{method}
* @see module:Helper/Base.run
*/
await runHook(`${ns}:${camelCase(`before ${method}`)}`)
await me[ns][method]()
/**
* Run after ```{method}``` is executed within ```{ns}``` context
*
* - ```{ns}``` - namespace
* - ```{method}``` - Accepted methods: ```Init``` or ```Start```
*
* @global
* @event {ns}:after{method}
* @see module:Helper/Base.run
*/
await runHook(`${ns}:${camelCase(`after ${method}`)}`)
if (method === 'start') freeze(me[ns].config)
})
/**
* Run after all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
*
* @global
* @event bajo:afterAll{method}
* @see module:Helper/Base.run
*/
await runHook(`bajo:${camelCase(`after all ${method}`)}`)
}
if (me.bajo.config.log.level === 'trace') {
let text = join(map(me.bajo.app.pluginPkgs, b => camelCase(b)))
text += ` (${me.bajo.app.pluginPkgs.length})`
me.bajo.log.trace('loadedPlugins%s', text)
} else me.bajo.log.debug('loadedPlugins%s', me.bajo.app.pluginPkgs.length)
}