bajo 2.16.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/class/{helper/bajo.js → _helper.js} +291 -27
- package/class/app.js +140 -12
- package/class/bajo.js +9 -79
- package/class/{app/cache.js → cache.js} +1 -2
- package/class/{app/log.js → log.js} +1 -1
- package/class/plugin.js +1 -1
- package/lib/import-module.js +1 -1
- package/package.json +1 -1
- package/wiki/CHANGES.md +11 -0
- package/class/helper/app.js +0 -73
- package/class/helper/base.js +0 -222
- /package/class/{plugin/err.js → err.js} +0 -0
- /package/class/{plugin/print.js → print.js} +0 -0
- /package/class/{plugin/tools.js → tools.js} +0 -0
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
import Print from '
|
|
2
|
-
import Log from '
|
|
1
|
+
import Print from './print.js'
|
|
2
|
+
import Log from './log.js'
|
|
3
3
|
import os from 'os'
|
|
4
4
|
import fs from 'fs-extra'
|
|
5
5
|
import lodash from 'lodash'
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
import semver from 'semver'
|
|
7
|
+
import aneka from 'aneka/index.js'
|
|
8
|
+
import outmatch from 'outmatch'
|
|
9
|
+
import fastGlob from 'fast-glob'
|
|
10
|
+
import { sprintf } from 'sprintf-js'
|
|
11
|
+
import dayjs from 'dayjs'
|
|
12
|
+
import utc from 'dayjs/plugin/utc.js'
|
|
13
|
+
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
|
|
14
|
+
import localizedFormat from 'dayjs/plugin/localizedFormat.js'
|
|
15
|
+
import weekOfYear from 'dayjs/plugin/weekOfYear.js'
|
|
16
|
+
import freeze from '../lib/freeze.js'
|
|
17
|
+
import findDeep from '../lib/find-deep.js'
|
|
18
|
+
import omitDeep from 'omit-deep'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Internal helpers called by Bajo and other classes. It should remains
|
|
22
|
+
* hidden and not to be imported by any program.
|
|
23
|
+
*
|
|
24
|
+
* @module Helper
|
|
25
|
+
*/
|
|
13
26
|
|
|
14
27
|
const {
|
|
15
|
-
|
|
16
|
-
isFunction,
|
|
17
|
-
isPlainObject,
|
|
18
|
-
map,
|
|
19
|
-
pick,
|
|
20
|
-
values,
|
|
21
|
-
keys,
|
|
22
|
-
set,
|
|
23
|
-
get,
|
|
24
|
-
without,
|
|
25
|
-
uniq,
|
|
26
|
-
camelCase,
|
|
27
|
-
isEmpty
|
|
28
|
+
merge, forOwn, groupBy, find, reduce, map, trim, keys, intersection, each,
|
|
29
|
+
camelCase, get, orderBy, isFunction, isPlainObject, pick, values, set, without, uniq, isEmpty
|
|
28
30
|
} = lodash
|
|
29
31
|
|
|
30
32
|
const omitted = ['spawn', 'cwd', 'name', 'alias', 'applet', 'a', 'plugins']
|
|
@@ -49,6 +51,19 @@ const defConfig = {
|
|
|
49
51
|
retain: 5
|
|
50
52
|
}
|
|
51
53
|
},
|
|
54
|
+
dump: {
|
|
55
|
+
depth: 2,
|
|
56
|
+
compact: false,
|
|
57
|
+
colors: true,
|
|
58
|
+
breakLength: 80,
|
|
59
|
+
caller: true,
|
|
60
|
+
frame: {
|
|
61
|
+
titleAllignment: 'center',
|
|
62
|
+
padding: 1,
|
|
63
|
+
margin: 1,
|
|
64
|
+
borderStyle: 'round'
|
|
65
|
+
}
|
|
66
|
+
},
|
|
52
67
|
lang: Intl.DateTimeFormat().resolvedOptions().lang ?? 'en-US',
|
|
53
68
|
intl: {
|
|
54
69
|
supported: ['en-US', 'id'],
|
|
@@ -90,12 +105,64 @@ const defMain = `async function factory (pkgName) {
|
|
|
90
105
|
export default factory
|
|
91
106
|
`
|
|
92
107
|
|
|
108
|
+
export function outmatchNs (source, pattern) {
|
|
109
|
+
const { breakNsPath } = this.bajo
|
|
110
|
+
const [src, subSrc] = source.split(':')
|
|
111
|
+
if (!subSrc) return pattern === src
|
|
112
|
+
try {
|
|
113
|
+
const { fullNs, path } = breakNsPath(pattern)
|
|
114
|
+
const isMatch = outmatch(path)
|
|
115
|
+
return src === fullNs && isMatch(subSrc)
|
|
116
|
+
} catch (err) {
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function parseObject (obj, options = {}) {
|
|
122
|
+
const me = this
|
|
123
|
+
const { ns = 'bajo', lang } = options
|
|
124
|
+
options.translator = {
|
|
125
|
+
lang,
|
|
126
|
+
prefix: 't:',
|
|
127
|
+
handler: val => {
|
|
128
|
+
const [text, ...args] = val.split('|')
|
|
129
|
+
args.push({ lang })
|
|
130
|
+
return me[ns].t(text, ...args)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return aneka.parseObject(obj, options)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
dayjs.extend(utc)
|
|
137
|
+
dayjs.extend(customParseFormat)
|
|
138
|
+
dayjs.extend(localizedFormat)
|
|
139
|
+
dayjs.extend(weekOfYear)
|
|
140
|
+
|
|
93
141
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* @
|
|
142
|
+
* @typedef {Object} TAppLib
|
|
143
|
+
* @property {Object} _ - Access to {@link https://lodash.com|lodash}
|
|
144
|
+
* @property {Object} fs - Access to {@link https://github.com/jprichardson/node-fs-extra|fs-extra}
|
|
145
|
+
* @property {Object} fastGlob - Access to {@link https://github.com/mrmlnc/fast-glob|fast-glob}
|
|
146
|
+
* @property {Object} sprintf - Access to {@link https://github.com/alexei/sprintf.js|sprintf}
|
|
147
|
+
* @property {Object} aneka - Access to {@link https://github.com/ardhi/aneka|aneka}
|
|
148
|
+
* @property {Object} outmatch - Access to {@link https://github.com/axtgr/outmatch|outmatch}
|
|
149
|
+
* @property {Object} dayjs - Access to {@link https://day.js.org|dayjs} with utc & customParseFormat plugin already applied
|
|
150
|
+
* @property {Object} freeze
|
|
151
|
+
* @property {Object} findDeep
|
|
152
|
+
* @see App
|
|
98
153
|
*/
|
|
154
|
+
export const lib = {
|
|
155
|
+
_: lodash,
|
|
156
|
+
fs,
|
|
157
|
+
fastGlob,
|
|
158
|
+
sprintf,
|
|
159
|
+
outmatch,
|
|
160
|
+
dayjs,
|
|
161
|
+
aneka,
|
|
162
|
+
freeze,
|
|
163
|
+
findDeep,
|
|
164
|
+
omitDeep
|
|
165
|
+
}
|
|
99
166
|
|
|
100
167
|
/**
|
|
101
168
|
* Building bajo base config. Mostly dealing with directory setups:
|
|
@@ -112,7 +179,7 @@ export async function buildBaseConfig () {
|
|
|
112
179
|
const { defaultsDeep, textToArray, currentLoc, resolvePath } = this.app.lib.aneka
|
|
113
180
|
this.config = defaultsDeep({}, this.app.argv._, this.app.envVars._)
|
|
114
181
|
set(this, 'dir.base', this.app.dir)
|
|
115
|
-
const path = currentLoc(import.meta).dir + '
|
|
182
|
+
const path = currentLoc(import.meta).dir + '/..'
|
|
116
183
|
set(this, 'dir.pkg', resolvePath(path))
|
|
117
184
|
if (get(this, 'config.dir.data')) set(this, 'dir.data', this.config.dir.data)
|
|
118
185
|
if (!get(this, 'dir.data')) set(this, 'dir.data', `${this.dir.base}/data`)
|
|
@@ -278,6 +345,203 @@ export async function bootOrder () {
|
|
|
278
345
|
freeze(this.config)
|
|
279
346
|
}
|
|
280
347
|
|
|
348
|
+
/**
|
|
349
|
+
* Build configurations
|
|
350
|
+
*
|
|
351
|
+
* @async
|
|
352
|
+
*/
|
|
353
|
+
export async function buildConfigs () {
|
|
354
|
+
this.bajo.log.debug('readConfigs')
|
|
355
|
+
for (const ns of this.getAllNs()) {
|
|
356
|
+
await this[ns].loadConfig()
|
|
357
|
+
this[ns].print = new Print(this[ns])
|
|
358
|
+
this.loadIntl(ns)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Ensure for names and aliases to be unique and no clashes with other plugins
|
|
364
|
+
*
|
|
365
|
+
* @async
|
|
366
|
+
*/
|
|
367
|
+
export async function checkNameAliases () {
|
|
368
|
+
this.bajo.log.debug('checkAliasNameClash')
|
|
369
|
+
const refs = []
|
|
370
|
+
for (const pkg of this.bajo.app.pluginPkgs) {
|
|
371
|
+
const plugin = this.bajo.app[camelCase(pkg)]
|
|
372
|
+
const { ns, alias } = plugin
|
|
373
|
+
let item = find(refs, { ns })
|
|
374
|
+
if (item) throw this.bajo.error('pluginNameClash%s%s%s%s', ns, pkg, item.ns, item.pkg, { code: 'BAJO_NAME_CLASH' })
|
|
375
|
+
item = find(refs, { alias })
|
|
376
|
+
if (item) throw this.bajo.error('pluginNameClash%s%s%s%s', alias, pkg, item.alias, item.pkg, { code: 'BAJO_ALIAS_CLASH' })
|
|
377
|
+
refs.push({ ns, alias, pkg })
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Ensure dependencies are met
|
|
383
|
+
*
|
|
384
|
+
* @async
|
|
385
|
+
*/
|
|
386
|
+
export async function checkDependencies () {
|
|
387
|
+
const { join } = this.bajo
|
|
388
|
+
this.bajo.log.debug('checkDeps')
|
|
389
|
+
for (const pkg of this.bajo.app.pluginPkgs) {
|
|
390
|
+
const plugin = this.bajo.app[camelCase(pkg)]
|
|
391
|
+
const { ns, dependencies } = plugin
|
|
392
|
+
this.bajo.log.trace('- %s', ns)
|
|
393
|
+
const odep = reduce(dependencies, (o, k) => {
|
|
394
|
+
const item = map(k.split('@'), m => trim(m))
|
|
395
|
+
if (k[0] === '@') o['@' + item[1]] = item[2]
|
|
396
|
+
else o[item[0]] = item[1]
|
|
397
|
+
return o
|
|
398
|
+
}, {})
|
|
399
|
+
const deps = keys(odep)
|
|
400
|
+
if (deps.length > 0) {
|
|
401
|
+
if (intersection(this.bajo.app.pluginPkgs, deps).length !== deps.length) {
|
|
402
|
+
throw this.bajo.error('dependencyUnfulfilled%s%s', pkg, join(deps), { code: 'BAJO_DEPENDENCY' })
|
|
403
|
+
}
|
|
404
|
+
each(deps, d => {
|
|
405
|
+
if (!odep[d]) return
|
|
406
|
+
const ver = get(this.bajo.app[camelCase(d)], 'pkg.version')
|
|
407
|
+
if (!ver) return
|
|
408
|
+
if (!semver.satisfies(ver, odep[d])) {
|
|
409
|
+
throw this.bajo.error('semverCheckFailed%s%s', pkg, `${d}@${odep[d]}`, { code: 'BAJO_DEPENDENCY_SEMVER' })
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Collect and build hooks and push them to the bajo's hook system
|
|
418
|
+
*
|
|
419
|
+
* @async
|
|
420
|
+
* @fires bajo:afterCollectHooks
|
|
421
|
+
*/
|
|
422
|
+
export async function collectHooks () {
|
|
423
|
+
const { eachPlugins, runHook, isLogInRange, importModule } = this.bajo
|
|
424
|
+
const { isArray, isPlainObject } = this.lib._
|
|
425
|
+
const me = this // "this" is "app"
|
|
426
|
+
me.bajo.log.trace('collecting%s', this.t('hooks'))
|
|
427
|
+
await eachPlugins(async function ({ dir, file }) {
|
|
428
|
+
let mod = await importModule(file, { asHandler: true })
|
|
429
|
+
if (!mod) return undefined
|
|
430
|
+
if (file.includes('hook.js')) mod = await mod.handler.call(this)
|
|
431
|
+
if (isArray(mod)) {
|
|
432
|
+
for (const m of mod) {
|
|
433
|
+
if (!isPlainObject(m)) continue
|
|
434
|
+
if (!m.name) throw me.bajo.error('missing%s%s', 'name', file)
|
|
435
|
+
if (isArray(m.name)) {
|
|
436
|
+
for (const name of m.name) {
|
|
437
|
+
me.bajo.hooks.push(merge({}, m, { name, src: this.ns }))
|
|
438
|
+
}
|
|
439
|
+
} else {
|
|
440
|
+
m.src = this.ns
|
|
441
|
+
me.bajo.hooks.push(m)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
const _file = file.replace(dir + '/hook/', '').replace('.js', '')
|
|
446
|
+
let [names, path] = _file.split('@')
|
|
447
|
+
names = names.split('$').map(n => trim(n))
|
|
448
|
+
for (let name of names) {
|
|
449
|
+
name = name.split('.').map(n => camelCase(n)).join('.')
|
|
450
|
+
const m = merge({}, mod, { name: `${name}:${camelCase(path)}`, src: this.ns })
|
|
451
|
+
me.bajo.hooks.push(m)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}, { glob: ['hook/*.js', 'hook.js'], prefix: me.bajo.ns })
|
|
455
|
+
// for log trace purpose only
|
|
456
|
+
if (isLogInRange('trace')) {
|
|
457
|
+
const items = groupBy(me.bajo.hooks, item => item.name)
|
|
458
|
+
forOwn(items, (v, k) => {
|
|
459
|
+
const [name, path] = k.split(':')
|
|
460
|
+
me.bajo.log.trace('- %s:%s (%d)', name, path, v.length)
|
|
461
|
+
})
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Run after hooks are collected
|
|
466
|
+
*
|
|
467
|
+
* @global
|
|
468
|
+
* @event bajo:afterCollectHooks
|
|
469
|
+
* @param {Object[]} hooks - Array of hook objects
|
|
470
|
+
* @see {@tutorial hook}
|
|
471
|
+
* @see module:Helper/Base.collectHooks
|
|
472
|
+
*/
|
|
473
|
+
await runHook('bajo:afterCollectHooks', this.bajo.hooks)
|
|
474
|
+
me.bajo.log.debug('collected%s%d', this.t('hooks'), me.bajo.hooks.length)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Finally, run all plugins
|
|
479
|
+
*
|
|
480
|
+
* @async
|
|
481
|
+
* @fires bajo:beforeAll{method}
|
|
482
|
+
* @fires {ns}:before{method}
|
|
483
|
+
* @fires {ns}:after{method}
|
|
484
|
+
* @fires bajo:afterAll{method}
|
|
485
|
+
*/
|
|
486
|
+
export async function run () {
|
|
487
|
+
const me = this
|
|
488
|
+
const { runHook, eachPlugins, join } = me.bajo
|
|
489
|
+
const { freeze } = me.lib
|
|
490
|
+
const methods = ['init']
|
|
491
|
+
if (!me.applet) methods.push('start')
|
|
492
|
+
for (const method of methods) {
|
|
493
|
+
/**
|
|
494
|
+
* Run before all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
|
|
495
|
+
*
|
|
496
|
+
* @global
|
|
497
|
+
* @event bajo:beforeAll{method}
|
|
498
|
+
* @param {string} method - Accepted methods: ```Init```, ```Start```
|
|
499
|
+
* @see module:Helper/Base.run
|
|
500
|
+
*/
|
|
501
|
+
await runHook(`bajo:${camelCase(`before all ${method}`)}`)
|
|
502
|
+
await eachPlugins(async function () {
|
|
503
|
+
const { ns } = this
|
|
504
|
+
/**
|
|
505
|
+
* Run before ```{method}``` is executed within ```{ns}``` context
|
|
506
|
+
*
|
|
507
|
+
* - ```{ns}``` - namespace
|
|
508
|
+
* - ```{method}``` - Accepted methods: ```Init``` or ```Start```
|
|
509
|
+
*
|
|
510
|
+
* @global
|
|
511
|
+
* @event {ns}:before{method}
|
|
512
|
+
* @see module:Helper/Base.run
|
|
513
|
+
*/
|
|
514
|
+
await runHook(`${ns}:${camelCase(`before ${method}`)}`)
|
|
515
|
+
await me[ns][method]()
|
|
516
|
+
/**
|
|
517
|
+
* Run after ```{method}``` is executed within ```{ns}``` context
|
|
518
|
+
*
|
|
519
|
+
* - ```{ns}``` - namespace
|
|
520
|
+
* - ```{method}``` - Accepted methods: ```Init``` or ```Start```
|
|
521
|
+
*
|
|
522
|
+
* @global
|
|
523
|
+
* @event {ns}:after{method}
|
|
524
|
+
* @see module:Helper/Base.run
|
|
525
|
+
*/
|
|
526
|
+
await runHook(`${ns}:${camelCase(`after ${method}`)}`)
|
|
527
|
+
if (method === 'start') freeze(me[ns].config)
|
|
528
|
+
})
|
|
529
|
+
/**
|
|
530
|
+
* Run after all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
|
|
531
|
+
*
|
|
532
|
+
* @global
|
|
533
|
+
* @event bajo:afterAll{method}
|
|
534
|
+
* @see module:Helper/Base.run
|
|
535
|
+
*/
|
|
536
|
+
await runHook(`bajo:${camelCase(`after all ${method}`)}`)
|
|
537
|
+
}
|
|
538
|
+
if (me.bajo.config.log.level === 'trace') {
|
|
539
|
+
let text = join(map(me.bajo.app.pluginPkgs, b => camelCase(b)))
|
|
540
|
+
text += ` (${me.bajo.app.pluginPkgs.length})`
|
|
541
|
+
me.bajo.log.trace('loadedPlugins%s', text)
|
|
542
|
+
} else me.bajo.log.debug('loadedPlugins%s', me.bajo.app.pluginPkgs.length)
|
|
543
|
+
}
|
|
544
|
+
|
|
281
545
|
/**
|
|
282
546
|
* Iterate through all plugins loaded and do:
|
|
283
547
|
*
|
package/class/app.js
CHANGED
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
import util from 'util'
|
|
2
2
|
import Bajo from './bajo.js'
|
|
3
3
|
import Base from './base.js'
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { outmatchNs, parseObject, lib } from './
|
|
4
|
+
import Cache from './cache.js'
|
|
5
|
+
import Tools from './tools.js'
|
|
6
|
+
import Plugin from './plugin.js'
|
|
7
|
+
import { outmatchNs, parseObject, lib, runAsApplet } from './_helper.js'
|
|
8
8
|
import { fileURLToPath } from 'url'
|
|
9
9
|
|
|
10
10
|
const { camelCase, isPlainObject, get, reverse, map, last, without, set } = lib._
|
|
11
|
-
const { pascalCase
|
|
11
|
+
const { pascalCase } = lib.aneka
|
|
12
12
|
let unknownLangWarning = false
|
|
13
13
|
|
|
14
|
+
function getCallerFilename () {
|
|
15
|
+
const originalFunc = Error.prepareStackTrace
|
|
16
|
+
let callerfile
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const err = new Error()
|
|
20
|
+
Error.prepareStackTrace = (_, stack) => stack
|
|
21
|
+
const currentfile = err.stack.shift().getFileName()
|
|
22
|
+
|
|
23
|
+
while (err.stack.length) {
|
|
24
|
+
callerfile = err.stack.shift().getFileName()
|
|
25
|
+
if (currentfile !== callerfile) break
|
|
26
|
+
}
|
|
27
|
+
} catch (e) {}
|
|
28
|
+
|
|
29
|
+
Error.prepareStackTrace = originalFunc
|
|
30
|
+
return callerfile
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
/**
|
|
15
34
|
* @typedef {Object} TAppEnv
|
|
16
35
|
* @property {string} dev=development
|
|
@@ -238,23 +257,132 @@ class App {
|
|
|
238
257
|
}
|
|
239
258
|
|
|
240
259
|
/**
|
|
241
|
-
*
|
|
260
|
+
* Get loaded plugins
|
|
261
|
+
*
|
|
262
|
+
* @method
|
|
263
|
+
* @param {string[]} [nss] - Array of namespaces. If empty, it returns all loaded plugins
|
|
264
|
+
* @returns {TPlugin[]}
|
|
265
|
+
*/
|
|
266
|
+
getPlugins = (nss) => {
|
|
267
|
+
const allNs = nss ?? this.getAllNs()
|
|
268
|
+
return allNs.map(ns => this[ns])
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get all plugins loaded plugins
|
|
273
|
+
*
|
|
274
|
+
* @method
|
|
275
|
+
* @returns {TPlugin[]}
|
|
276
|
+
*/
|
|
277
|
+
getAllPlugins = () => {
|
|
278
|
+
return this.getPlugins()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get plugin by name
|
|
283
|
+
*
|
|
284
|
+
* @method
|
|
285
|
+
* @param {string} name - Plugin name/namespace or alias
|
|
286
|
+
* @param {boolean} [silent] - If ```true```, silently return undefined even on error
|
|
287
|
+
* @returns {Object} Plugin object
|
|
288
|
+
*/
|
|
289
|
+
getPlugin = (name, silent) => {
|
|
290
|
+
if (!this[name]) {
|
|
291
|
+
// alias?
|
|
292
|
+
let plugin
|
|
293
|
+
for (const key in this) {
|
|
294
|
+
const item = this[key]
|
|
295
|
+
if (item instanceof Plugin && (item.alias === name || item.pkgName === name)) {
|
|
296
|
+
plugin = item
|
|
297
|
+
break
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (!plugin) {
|
|
301
|
+
if (silent) return false
|
|
302
|
+
throw this.bajo.error('pluginWithNameAliasNotLoaded%s', name)
|
|
303
|
+
}
|
|
304
|
+
name = plugin.ns
|
|
305
|
+
}
|
|
306
|
+
return this[name]
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get plugin data directory
|
|
311
|
+
*
|
|
312
|
+
* @method
|
|
313
|
+
* @param {string} name - Plugin name (namespace) or alias
|
|
314
|
+
* @param {boolean} [ensureDir=true] - Set ```true``` (default) to ensure directory is existed
|
|
315
|
+
* @returns {string}
|
|
316
|
+
*/
|
|
317
|
+
getPluginDataDir = (name, ensureDir = true) => {
|
|
318
|
+
const { fs } = this.lib
|
|
319
|
+
const plugin = this.getPlugin(name)
|
|
320
|
+
const dir = `${this.bajo.dir.data}/plugins/${plugin.ns}`
|
|
321
|
+
if (ensureDir) fs.ensureDirSync(dir)
|
|
322
|
+
return dir
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Resolve file path from:
|
|
327
|
+
*
|
|
328
|
+
* - local/absolute file
|
|
329
|
+
* - TNsPath (```myPlugin:/path/to/file.txt```)
|
|
330
|
+
* - file under node_modules, e.g. ```myPlugin:node_modules/some-package/file.txt```
|
|
331
|
+
*
|
|
332
|
+
* @method
|
|
333
|
+
* @param {string} file - File path, see above for supported types
|
|
334
|
+
* @returns {string} Resolved file path
|
|
335
|
+
*/
|
|
336
|
+
getPluginFile = (file) => {
|
|
337
|
+
const { currentLoc } = this.lib.aneka
|
|
338
|
+
const { fs } = this.lib
|
|
339
|
+
const { trim } = this.lib._
|
|
340
|
+
if (!this) return file
|
|
341
|
+
if (file[0] === '.') file = `${currentLoc(import.meta).dir}/${trim(file.slice(1), '/')}`
|
|
342
|
+
if (file.includes(':')) {
|
|
343
|
+
if (file.slice(1, 2) === ':') return file // windows fs
|
|
344
|
+
const { ns, path } = this.bajo.breakNsPath(file, false)
|
|
345
|
+
if (ns !== 'file' && this && this[ns] && ns.length > 1) {
|
|
346
|
+
file = `${this[ns].dir.pkg}${path}`
|
|
347
|
+
if (path.startsWith('node_modules/')) {
|
|
348
|
+
file = `${this[ns].dir.pkg}/${path}`
|
|
349
|
+
if (!fs.existsSync(file)) file = `${this[ns].dir.pkg}/../${path.slice('node_modules/'.length)}`
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return file
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Dumping variable on screen. Like ```console.log``` with configurable options. Useful for quick debugging and testing. You can also use it to dump variables in production without worrying about performance because it is using Bajo's built-in cache to store the result of util's inspect, so it will only be processed once for each unique variable.
|
|
358
|
+
*
|
|
359
|
+
* Any argument passed to this method will be displayed on screen.
|
|
360
|
+
* If the last argument is a boolean ```true```, app will quit rightaway after dumping.
|
|
361
|
+
*
|
|
362
|
+
* If you have ```bajoCli``` plugin installed, variables will be displayed in a nice box using ```boxen``` package.
|
|
363
|
+
* Otherwise, it will fallback to ```console.log``` with util's inspect result.
|
|
364
|
+
*
|
|
365
|
+
* To have more control on how the variable is displayed, you can set options in Bajo's config under ```dump``` key.
|
|
366
|
+
* See {@link Bajo#config} for details.
|
|
242
367
|
*
|
|
243
368
|
* @method
|
|
244
|
-
* @param {...any} args -
|
|
369
|
+
* @param {...any} args - Variables to dump
|
|
245
370
|
*/
|
|
246
371
|
dump = (...args) => {
|
|
247
372
|
let caller = getCallerFilename()
|
|
248
373
|
caller = caller ? fileURLToPath(caller) : 'Unavailable'
|
|
249
|
-
const
|
|
374
|
+
const opts = last(args)
|
|
375
|
+
const terminate = isPlainObject(opts) && opts.abort
|
|
250
376
|
if (terminate) args.pop()
|
|
251
377
|
const value = args.length === 1 ? args[0] : args
|
|
378
|
+
const options = { ...this.bajo.config.dump }
|
|
252
379
|
if (this.boxen) {
|
|
253
|
-
const result = util.inspect(value,
|
|
254
|
-
const
|
|
255
|
-
|
|
380
|
+
const result = util.inspect(value, options)
|
|
381
|
+
const opts = { ...this.bajo.config.dump.frame }
|
|
382
|
+
if (options.caller) opts.title = `Caller: ${caller}`
|
|
383
|
+
console.log(this.boxen(result, opts))
|
|
256
384
|
} else {
|
|
257
|
-
const result = util.inspect([caller, value]
|
|
385
|
+
const result = util.inspect(options.caller ? [caller, value] : value, options)
|
|
258
386
|
console.log(result)
|
|
259
387
|
}
|
|
260
388
|
if (terminate) process.exit('1')
|
package/class/bajo.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Tools from './tools.js'
|
|
1
2
|
import Plugin from './plugin.js'
|
|
2
3
|
import increment from 'add-filename-increment'
|
|
3
4
|
import fs from 'fs-extra'
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
bootOrder,
|
|
22
23
|
bootPlugins,
|
|
23
24
|
exitHandler
|
|
24
|
-
} from './
|
|
25
|
+
} from './_helper.js'
|
|
25
26
|
|
|
26
27
|
const require = createRequire(import.meta.url)
|
|
27
28
|
|
|
@@ -32,7 +33,7 @@ const {
|
|
|
32
33
|
last, get, has, values, dropRight, pick
|
|
33
34
|
} = lodash
|
|
34
35
|
|
|
35
|
-
const { resolvePath
|
|
36
|
+
const { resolvePath } = aneka
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* The Core. The main engine. The one and only plugin that control app's boot process and
|
|
@@ -171,7 +172,7 @@ class Bajo extends Plugin {
|
|
|
171
172
|
[ns, subNs, subSubNs] = ns.split('.')
|
|
172
173
|
if (checkNs) {
|
|
173
174
|
if (!this.app[ns]) {
|
|
174
|
-
const plugin = this.getPlugin(ns)
|
|
175
|
+
const plugin = this.app.getPlugin(ns)
|
|
175
176
|
if (plugin) ns = plugin.ns
|
|
176
177
|
}
|
|
177
178
|
if (!this.app[ns]) throw this.error('unknownPluginOrNotLoaded%s')
|
|
@@ -297,7 +298,7 @@ class Bajo extends Plugin {
|
|
|
297
298
|
callHandler = async (item, ...args) => {
|
|
298
299
|
let result
|
|
299
300
|
let scope = this
|
|
300
|
-
if (item instanceof Plugin) {
|
|
301
|
+
if (item instanceof Tools || item instanceof Plugin) {
|
|
301
302
|
scope = item
|
|
302
303
|
item = args.shift()
|
|
303
304
|
}
|
|
@@ -571,77 +572,6 @@ class Bajo extends Plugin {
|
|
|
571
572
|
return resolvePath(path.dirname(dir))
|
|
572
573
|
}
|
|
573
574
|
|
|
574
|
-
/**
|
|
575
|
-
* Get plugin data directory
|
|
576
|
-
*
|
|
577
|
-
* @method
|
|
578
|
-
* @param {string} name - Plugin name (namespace) or alias
|
|
579
|
-
* @param {boolean} [ensureDir=true] - Set ```true``` (default) to ensure directory is existed
|
|
580
|
-
* @returns {string}
|
|
581
|
-
*/
|
|
582
|
-
getPluginDataDir = (name, ensureDir = true) => {
|
|
583
|
-
const plugin = this.getPlugin(name)
|
|
584
|
-
const dir = `${this.app.bajo.dir.data}/plugins/${plugin.ns}`
|
|
585
|
-
if (ensureDir) fs.ensureDirSync(dir)
|
|
586
|
-
return dir
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Resolve file path from:
|
|
591
|
-
*
|
|
592
|
-
* - local/absolute file
|
|
593
|
-
* - TNsPath (```myPlugin:/path/to/file.txt```)
|
|
594
|
-
* - file under node_modules, e.g. ```myPlugin:node_modules/some-package/file.txt```
|
|
595
|
-
*
|
|
596
|
-
* @method
|
|
597
|
-
* @param {string} file - File path, see above for supported types
|
|
598
|
-
* @returns {string} Resolved file path
|
|
599
|
-
*/
|
|
600
|
-
getPluginFile = (file) => {
|
|
601
|
-
if (!this) return file
|
|
602
|
-
if (file[0] === '.') file = `${currentLoc(import.meta).dir}/${trim(file.slice(1), '/')}`
|
|
603
|
-
if (file.includes(':')) {
|
|
604
|
-
if (file.slice(1, 2) === ':') return file // windows fs
|
|
605
|
-
const { ns, path } = this.breakNsPath(file)
|
|
606
|
-
if (ns !== 'file' && this && this.app && this.app[ns] && ns.length > 1) {
|
|
607
|
-
file = `${this.app[ns].dir.pkg}${path}`
|
|
608
|
-
if (path.startsWith('node_modules/')) {
|
|
609
|
-
file = `${this.app[ns].dir.pkg}/${path}`
|
|
610
|
-
if (!fs.existsSync(file)) file = `${this.app[ns].dir.pkg}/../${path.slice('node_modules/'.length)}`
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
return file
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* Get plugin by name
|
|
619
|
-
*
|
|
620
|
-
* @method
|
|
621
|
-
* @param {string} name - Plugin name/namespace or alias
|
|
622
|
-
* @param {boolean} [silent] - If ```true```, silently return undefined even on error
|
|
623
|
-
* @returns {Object} Plugin object
|
|
624
|
-
*/
|
|
625
|
-
getPlugin = (name, silent) => {
|
|
626
|
-
if (!this.app[name]) {
|
|
627
|
-
// alias?
|
|
628
|
-
let plugin
|
|
629
|
-
for (const key in this.app) {
|
|
630
|
-
const item = this.app[key]
|
|
631
|
-
if (item instanceof Plugin && (item.alias === name || item.pkgName === name)) {
|
|
632
|
-
plugin = item
|
|
633
|
-
break
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
if (!plugin) {
|
|
637
|
-
if (silent) return false
|
|
638
|
-
throw this.error('pluginWithNameAliasNotLoaded%s', name)
|
|
639
|
-
}
|
|
640
|
-
name = plugin.ns
|
|
641
|
-
}
|
|
642
|
-
return this.app[name]
|
|
643
|
-
}
|
|
644
|
-
|
|
645
575
|
/**
|
|
646
576
|
* Import file/module from any loaded plugins.
|
|
647
577
|
*
|
|
@@ -729,7 +659,7 @@ class Bajo extends Plugin {
|
|
|
729
659
|
* @returns {boolean}
|
|
730
660
|
*/
|
|
731
661
|
isEmptyDir = async (dir, filterFn) => {
|
|
732
|
-
dir = resolvePath(this.getPluginFile(dir))
|
|
662
|
+
dir = resolvePath(this.app.getPluginFile(dir))
|
|
733
663
|
await fs.exists(dir)
|
|
734
664
|
return await emptyDir(dir, filterFn)
|
|
735
665
|
}
|
|
@@ -915,7 +845,7 @@ class Bajo extends Plugin {
|
|
|
915
845
|
await this.runHook('bajo:beforeReadConfig', file, options)
|
|
916
846
|
parserOpts.readFromFile = true
|
|
917
847
|
if (!ns) ns = this.ns
|
|
918
|
-
file = resolvePath(this.getPluginFile(file))
|
|
848
|
+
file = resolvePath(this.app.getPluginFile(file))
|
|
919
849
|
let ext = path.extname(file)
|
|
920
850
|
const fname = path.dirname(file) + '/' + path.basename(file, ext)
|
|
921
851
|
ext = ext.toLowerCase()
|
|
@@ -1064,8 +994,8 @@ class Bajo extends Plugin {
|
|
|
1064
994
|
* @returns {string} Full file path
|
|
1065
995
|
*/
|
|
1066
996
|
saveAsDownload = async (file, item, printSaved = true) => {
|
|
1067
|
-
const { print
|
|
1068
|
-
const fname = increment(`${getPluginDataDir(this.ns)}/download/${trim(file, '/')}`, { fs: true })
|
|
997
|
+
const { print } = this.app.bajo
|
|
998
|
+
const fname = increment(`${this.app.getPluginDataDir(this.ns)}/download/${trim(file, '/')}`, { fs: true })
|
|
1069
999
|
const dir = path.dirname(fname)
|
|
1070
1000
|
if (!fs.existsSync(dir)) fs.ensureDirSync(dir)
|
|
1071
1001
|
await fs.writeFile(fname, item, 'utf8')
|
package/class/plugin.js
CHANGED
package/lib/import-module.js
CHANGED
|
@@ -43,7 +43,7 @@ async function importModule (file, { asDefaultImport = true, asHandler, noCache
|
|
|
43
43
|
return imported
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
if (this) file = this.getPluginFile(file)
|
|
46
|
+
if (this) file = this.app.getPluginFile(file)
|
|
47
47
|
if (!fs.existsSync(file)) return
|
|
48
48
|
let mod = await load(file, asDefaultImport, noCache)
|
|
49
49
|
if (!asHandler) return mod
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-06-12
|
|
4
|
+
|
|
5
|
+
- [2.18.0] Move plugin related methods to ```app```
|
|
6
|
+
- [2.18.0] Last argument of ```app.dump()``` serves as options if it a plain object with certain keys
|
|
7
|
+
|
|
8
|
+
## 2026-06-03
|
|
9
|
+
|
|
10
|
+
- [2.17.0] Combine all helpers to ```_helper.js```
|
|
11
|
+
- [2.17.0] ```callHandler()``` now can accept an instance of ```Tools``` as its scope too
|
|
12
|
+
- [2.17.0] Bug fix in ```app.dump()```
|
|
13
|
+
|
|
3
14
|
## 2026-06-01
|
|
4
15
|
|
|
5
16
|
- [2.16.0] Upgrade ```aneka@0.14.0```
|
package/class/helper/app.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import aneka from 'aneka/index.js'
|
|
2
|
-
import outmatch from 'outmatch'
|
|
3
|
-
import lodash from 'lodash'
|
|
4
|
-
import fs from 'fs-extra'
|
|
5
|
-
import fastGlob from 'fast-glob'
|
|
6
|
-
import { sprintf } from 'sprintf-js'
|
|
7
|
-
import dayjs from 'dayjs'
|
|
8
|
-
import utc from 'dayjs/plugin/utc.js'
|
|
9
|
-
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
|
|
10
|
-
import localizedFormat from 'dayjs/plugin/localizedFormat.js'
|
|
11
|
-
import weekOfYear from 'dayjs/plugin/weekOfYear.js'
|
|
12
|
-
import freeze from '../../lib/freeze.js'
|
|
13
|
-
import findDeep from '../../lib/find-deep.js'
|
|
14
|
-
import omitDeep from 'omit-deep'
|
|
15
|
-
|
|
16
|
-
dayjs.extend(utc)
|
|
17
|
-
dayjs.extend(customParseFormat)
|
|
18
|
-
dayjs.extend(localizedFormat)
|
|
19
|
-
dayjs.extend(weekOfYear)
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @typedef {Object} TAppLib
|
|
23
|
-
* @property {Object} _ - Access to {@link https://lodash.com|lodash}
|
|
24
|
-
* @property {Object} fs - Access to {@link https://github.com/jprichardson/node-fs-extra|fs-extra}
|
|
25
|
-
* @property {Object} fastGlob - Access to {@link https://github.com/mrmlnc/fast-glob|fast-glob}
|
|
26
|
-
* @property {Object} sprintf - Access to {@link https://github.com/alexei/sprintf.js|sprintf}
|
|
27
|
-
* @property {Object} aneka - Access to {@link https://github.com/ardhi/aneka|aneka}
|
|
28
|
-
* @property {Object} outmatch - Access to {@link https://github.com/axtgr/outmatch|outmatch}
|
|
29
|
-
* @property {Object} dayjs - Access to {@link https://day.js.org|dayjs} with utc & customParseFormat plugin already applied
|
|
30
|
-
* @property {Object} freeze
|
|
31
|
-
* @property {Object} findDeep
|
|
32
|
-
* @see App
|
|
33
|
-
*/
|
|
34
|
-
export const lib = {
|
|
35
|
-
_: lodash,
|
|
36
|
-
fs,
|
|
37
|
-
fastGlob,
|
|
38
|
-
sprintf,
|
|
39
|
-
outmatch,
|
|
40
|
-
dayjs,
|
|
41
|
-
aneka,
|
|
42
|
-
freeze,
|
|
43
|
-
findDeep,
|
|
44
|
-
omitDeep
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function outmatchNs (source, pattern) {
|
|
48
|
-
const { breakNsPath } = this.bajo
|
|
49
|
-
const [src, subSrc] = source.split(':')
|
|
50
|
-
if (!subSrc) return pattern === src
|
|
51
|
-
try {
|
|
52
|
-
const { fullNs, path } = breakNsPath(pattern)
|
|
53
|
-
const isMatch = outmatch(path)
|
|
54
|
-
return src === fullNs && isMatch(subSrc)
|
|
55
|
-
} catch (err) {
|
|
56
|
-
return false
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function parseObject (obj, options = {}) {
|
|
61
|
-
const me = this
|
|
62
|
-
const { ns = 'bajo', lang } = options
|
|
63
|
-
options.translator = {
|
|
64
|
-
lang,
|
|
65
|
-
prefix: 't:',
|
|
66
|
-
handler: val => {
|
|
67
|
-
const [text, ...args] = val.split('|')
|
|
68
|
-
args.push({ lang })
|
|
69
|
-
return me[ns].t(text, ...args)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return aneka.parseObject(obj, options)
|
|
73
|
-
}
|
package/class/helper/base.js
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import semver from 'semver'
|
|
2
|
-
import lodash from 'lodash'
|
|
3
|
-
import Print from '../plugin/print.js'
|
|
4
|
-
|
|
5
|
-
const {
|
|
6
|
-
merge,
|
|
7
|
-
forOwn,
|
|
8
|
-
groupBy,
|
|
9
|
-
find,
|
|
10
|
-
reduce,
|
|
11
|
-
map,
|
|
12
|
-
trim,
|
|
13
|
-
keys,
|
|
14
|
-
intersection,
|
|
15
|
-
each,
|
|
16
|
-
camelCase,
|
|
17
|
-
get
|
|
18
|
-
} = lodash
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Internal helpers called by Bajo & plugins that only used once for bootstrapping purpose.
|
|
22
|
-
* It should remains hidden and not to be imported by any program.
|
|
23
|
-
*
|
|
24
|
-
* @module Helper/Base
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Build configurations
|
|
29
|
-
*
|
|
30
|
-
* @async
|
|
31
|
-
*/
|
|
32
|
-
export async function buildConfigs () {
|
|
33
|
-
this.bajo.log.debug('readConfigs')
|
|
34
|
-
for (const ns of this.getAllNs()) {
|
|
35
|
-
await this[ns].loadConfig()
|
|
36
|
-
this[ns].print = new Print(this[ns])
|
|
37
|
-
this.loadIntl(ns)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Ensure for names and aliases to be unique and no clashes with other plugins
|
|
43
|
-
*
|
|
44
|
-
* @async
|
|
45
|
-
*/
|
|
46
|
-
export async function checkNameAliases () {
|
|
47
|
-
this.bajo.log.debug('checkAliasNameClash')
|
|
48
|
-
const refs = []
|
|
49
|
-
for (const pkg of this.bajo.app.pluginPkgs) {
|
|
50
|
-
const plugin = this.bajo.app[camelCase(pkg)]
|
|
51
|
-
const { ns, alias } = plugin
|
|
52
|
-
let item = find(refs, { ns })
|
|
53
|
-
if (item) throw this.bajo.error('pluginNameClash%s%s%s%s', ns, pkg, item.ns, item.pkg, { code: 'BAJO_NAME_CLASH' })
|
|
54
|
-
item = find(refs, { alias })
|
|
55
|
-
if (item) throw this.bajo.error('pluginNameClash%s%s%s%s', alias, pkg, item.alias, item.pkg, { code: 'BAJO_ALIAS_CLASH' })
|
|
56
|
-
refs.push({ ns, alias, pkg })
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Ensure dependencies are met
|
|
62
|
-
*
|
|
63
|
-
* @async
|
|
64
|
-
*/
|
|
65
|
-
export async function checkDependencies () {
|
|
66
|
-
const { join } = this.bajo
|
|
67
|
-
this.bajo.log.debug('checkDeps')
|
|
68
|
-
for (const pkg of this.bajo.app.pluginPkgs) {
|
|
69
|
-
const plugin = this.bajo.app[camelCase(pkg)]
|
|
70
|
-
const { ns, dependencies } = plugin
|
|
71
|
-
this.bajo.log.trace('- %s', ns)
|
|
72
|
-
const odep = reduce(dependencies, (o, k) => {
|
|
73
|
-
const item = map(k.split('@'), m => trim(m))
|
|
74
|
-
if (k[0] === '@') o['@' + item[1]] = item[2]
|
|
75
|
-
else o[item[0]] = item[1]
|
|
76
|
-
return o
|
|
77
|
-
}, {})
|
|
78
|
-
const deps = keys(odep)
|
|
79
|
-
if (deps.length > 0) {
|
|
80
|
-
if (intersection(this.bajo.app.pluginPkgs, deps).length !== deps.length) {
|
|
81
|
-
throw this.bajo.error('dependencyUnfulfilled%s%s', pkg, join(deps), { code: 'BAJO_DEPENDENCY' })
|
|
82
|
-
}
|
|
83
|
-
each(deps, d => {
|
|
84
|
-
if (!odep[d]) return
|
|
85
|
-
const ver = get(this.bajo.app[camelCase(d)], 'pkg.version')
|
|
86
|
-
if (!ver) return
|
|
87
|
-
if (!semver.satisfies(ver, odep[d])) {
|
|
88
|
-
throw this.bajo.error('semverCheckFailed%s%s', pkg, `${d}@${odep[d]}`, { code: 'BAJO_DEPENDENCY_SEMVER' })
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Collect and build hooks and push them to the bajo's hook system
|
|
97
|
-
*
|
|
98
|
-
* @async
|
|
99
|
-
* @fires bajo:afterCollectHooks
|
|
100
|
-
*/
|
|
101
|
-
export async function collectHooks () {
|
|
102
|
-
const { eachPlugins, runHook, isLogInRange, importModule } = this.bajo
|
|
103
|
-
const { isArray, isPlainObject } = this.lib._
|
|
104
|
-
const me = this // "this" is "app"
|
|
105
|
-
me.bajo.log.trace('collecting%s', this.t('hooks'))
|
|
106
|
-
await eachPlugins(async function ({ dir, file }) {
|
|
107
|
-
let mod = await importModule(file, { asHandler: true })
|
|
108
|
-
if (!mod) return undefined
|
|
109
|
-
if (file.includes('hook.js')) mod = await mod.handler.call(this)
|
|
110
|
-
if (isArray(mod)) {
|
|
111
|
-
for (const m of mod) {
|
|
112
|
-
if (!isPlainObject(m)) continue
|
|
113
|
-
if (!m.name) throw me.bajo.error('missing%s%s', 'name', file)
|
|
114
|
-
if (isArray(m.name)) {
|
|
115
|
-
for (const name of m.name) {
|
|
116
|
-
me.bajo.hooks.push(merge({}, m, { name, src: this.ns }))
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
119
|
-
m.src = this.ns
|
|
120
|
-
me.bajo.hooks.push(m)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
} else {
|
|
124
|
-
const _file = file.replace(dir + '/hook/', '').replace('.js', '')
|
|
125
|
-
let [names, path] = _file.split('@')
|
|
126
|
-
names = names.split('$').map(n => trim(n))
|
|
127
|
-
for (let name of names) {
|
|
128
|
-
name = name.split('.').map(n => camelCase(n)).join('.')
|
|
129
|
-
const m = merge({}, mod, { name: `${name}:${camelCase(path)}`, src: this.ns })
|
|
130
|
-
me.bajo.hooks.push(m)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}, { glob: ['hook/*.js', 'hook.js'], prefix: me.bajo.ns })
|
|
134
|
-
// for log trace purpose only
|
|
135
|
-
if (isLogInRange('trace')) {
|
|
136
|
-
const items = groupBy(me.bajo.hooks, item => item.name)
|
|
137
|
-
forOwn(items, (v, k) => {
|
|
138
|
-
const [name, path] = k.split(':')
|
|
139
|
-
me.bajo.log.trace('- %s:%s (%d)', name, path, v.length)
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Run after hooks are collected
|
|
145
|
-
*
|
|
146
|
-
* @global
|
|
147
|
-
* @event bajo:afterCollectHooks
|
|
148
|
-
* @param {Object[]} hooks - Array of hook objects
|
|
149
|
-
* @see {@tutorial hook}
|
|
150
|
-
* @see module:Helper/Base.collectHooks
|
|
151
|
-
*/
|
|
152
|
-
await runHook('bajo:afterCollectHooks', this.bajo.hooks)
|
|
153
|
-
me.bajo.log.debug('collected%s%d', this.t('hooks'), me.bajo.hooks.length)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Finally, run all plugins
|
|
158
|
-
*
|
|
159
|
-
* @async
|
|
160
|
-
* @fires bajo:beforeAll{method}
|
|
161
|
-
* @fires {ns}:before{method}
|
|
162
|
-
* @fires {ns}:after{method}
|
|
163
|
-
* @fires bajo:afterAll{method}
|
|
164
|
-
*/
|
|
165
|
-
export async function run () {
|
|
166
|
-
const me = this
|
|
167
|
-
const { runHook, eachPlugins, join } = me.bajo
|
|
168
|
-
const { freeze } = me.lib
|
|
169
|
-
const methods = ['init']
|
|
170
|
-
if (!me.applet) methods.push('start')
|
|
171
|
-
for (const method of methods) {
|
|
172
|
-
/**
|
|
173
|
-
* Run before all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
|
|
174
|
-
*
|
|
175
|
-
* @global
|
|
176
|
-
* @event bajo:beforeAll{method}
|
|
177
|
-
* @param {string} method - Accepted methods: ```Init```, ```Start```
|
|
178
|
-
* @see module:Helper/Base.run
|
|
179
|
-
*/
|
|
180
|
-
await runHook(`bajo:${camelCase(`before all ${method}`)}`)
|
|
181
|
-
await eachPlugins(async function () {
|
|
182
|
-
const { ns } = this
|
|
183
|
-
/**
|
|
184
|
-
* Run before ```{method}``` is executed within ```{ns}``` context
|
|
185
|
-
*
|
|
186
|
-
* - ```{ns}``` - namespace
|
|
187
|
-
* - ```{method}``` - Accepted methods: ```Init``` or ```Start```
|
|
188
|
-
*
|
|
189
|
-
* @global
|
|
190
|
-
* @event {ns}:before{method}
|
|
191
|
-
* @see module:Helper/Base.run
|
|
192
|
-
*/
|
|
193
|
-
await runHook(`${ns}:${camelCase(`before ${method}`)}`)
|
|
194
|
-
await me[ns][method]()
|
|
195
|
-
/**
|
|
196
|
-
* Run after ```{method}``` is executed within ```{ns}``` context
|
|
197
|
-
*
|
|
198
|
-
* - ```{ns}``` - namespace
|
|
199
|
-
* - ```{method}``` - Accepted methods: ```Init``` or ```Start```
|
|
200
|
-
*
|
|
201
|
-
* @global
|
|
202
|
-
* @event {ns}:after{method}
|
|
203
|
-
* @see module:Helper/Base.run
|
|
204
|
-
*/
|
|
205
|
-
await runHook(`${ns}:${camelCase(`after ${method}`)}`)
|
|
206
|
-
if (method === 'start') freeze(me[ns].config)
|
|
207
|
-
})
|
|
208
|
-
/**
|
|
209
|
-
* Run after all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
|
|
210
|
-
*
|
|
211
|
-
* @global
|
|
212
|
-
* @event bajo:afterAll{method}
|
|
213
|
-
* @see module:Helper/Base.run
|
|
214
|
-
*/
|
|
215
|
-
await runHook(`bajo:${camelCase(`after all ${method}`)}`)
|
|
216
|
-
}
|
|
217
|
-
if (me.bajo.config.log.level === 'trace') {
|
|
218
|
-
let text = join(map(me.bajo.app.pluginPkgs, b => camelCase(b)))
|
|
219
|
-
text += ` (${me.bajo.app.pluginPkgs.length})`
|
|
220
|
-
me.bajo.log.trace('loadedPlugins%s', text)
|
|
221
|
-
} else me.bajo.log.debug('loadedPlugins%s', me.bajo.app.pluginPkgs.length)
|
|
222
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|