bajo 2.16.0 → 2.17.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 +40 -11
- package/class/bajo.js +3 -2
- package/class/{app/log.js → log.js} +1 -1
- package/class/plugin.js +1 -1
- package/package.json +1 -1
- package/wiki/CHANGES.md +6 -0
- package/class/helper/app.js +0 -73
- package/class/helper/base.js +0 -222
- /package/class/{app/cache.js → cache.js} +0 -0
- /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,34 @@
|
|
|
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 './helper/app.js'
|
|
4
|
+
import Cache from './cache.js'
|
|
5
|
+
import Tools from './tools.js'
|
|
6
|
+
import { outmatchNs, parseObject, lib, runAsApplet } from './_helper.js'
|
|
8
7
|
import { fileURLToPath } from 'url'
|
|
9
8
|
|
|
10
9
|
const { camelCase, isPlainObject, get, reverse, map, last, without, set } = lib._
|
|
11
|
-
const { pascalCase
|
|
10
|
+
const { pascalCase } = lib.aneka
|
|
12
11
|
let unknownLangWarning = false
|
|
13
12
|
|
|
13
|
+
function getCallerFilename () {
|
|
14
|
+
const originalFunc = Error.prepareStackTrace
|
|
15
|
+
let callerfile
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const err = new Error()
|
|
19
|
+
Error.prepareStackTrace = (_, stack) => stack
|
|
20
|
+
const currentfile = err.stack.shift().getFileName()
|
|
21
|
+
|
|
22
|
+
while (err.stack.length) {
|
|
23
|
+
callerfile = err.stack.shift().getFileName()
|
|
24
|
+
if (currentfile !== callerfile) break
|
|
25
|
+
}
|
|
26
|
+
} catch (e) {}
|
|
27
|
+
|
|
28
|
+
Error.prepareStackTrace = originalFunc
|
|
29
|
+
return callerfile
|
|
30
|
+
}
|
|
31
|
+
|
|
14
32
|
/**
|
|
15
33
|
* @typedef {Object} TAppEnv
|
|
16
34
|
* @property {string} dev=development
|
|
@@ -238,10 +256,19 @@ class App {
|
|
|
238
256
|
}
|
|
239
257
|
|
|
240
258
|
/**
|
|
241
|
-
* Dumping variable on screen. Like ```console.log```
|
|
259
|
+
* 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.
|
|
260
|
+
*
|
|
261
|
+
* Any argument passed to this method will be displayed on screen.
|
|
262
|
+
* If the last argument is a boolean ```true```, app will quit rightaway after dumping.
|
|
263
|
+
*
|
|
264
|
+
* If you have ```bajoCli``` plugin installed, variables will be displayed in a nice box using ```boxen``` package.
|
|
265
|
+
* Otherwise, it will fallback to ```console.log``` with util's inspect result.
|
|
266
|
+
*
|
|
267
|
+
* To have more control on how the variable is displayed, you can set options in Bajo's config under ```dump``` key.
|
|
268
|
+
* See {@link Bajo#config} for details.
|
|
242
269
|
*
|
|
243
270
|
* @method
|
|
244
|
-
* @param {...any} args -
|
|
271
|
+
* @param {...any} args - Variables to dump
|
|
245
272
|
*/
|
|
246
273
|
dump = (...args) => {
|
|
247
274
|
let caller = getCallerFilename()
|
|
@@ -249,12 +276,14 @@ class App {
|
|
|
249
276
|
const terminate = last(args) === true
|
|
250
277
|
if (terminate) args.pop()
|
|
251
278
|
const value = args.length === 1 ? args[0] : args
|
|
279
|
+
const options = { ...this.bajo.config.dump }
|
|
252
280
|
if (this.boxen) {
|
|
253
|
-
const result = util.inspect(value,
|
|
254
|
-
const
|
|
255
|
-
|
|
281
|
+
const result = util.inspect(value, options)
|
|
282
|
+
const opts = { ...this.bajo.config.dump.frame }
|
|
283
|
+
if (options.caller) opts.title = `Caller: ${caller}`
|
|
284
|
+
console.log(this.boxen(result, opts))
|
|
256
285
|
} else {
|
|
257
|
-
const result = util.inspect([caller, value]
|
|
286
|
+
const result = util.inspect(options.caller ? [caller, value] : value, options)
|
|
258
287
|
console.log(result)
|
|
259
288
|
}
|
|
260
289
|
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
|
|
|
@@ -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
|
}
|
package/class/plugin.js
CHANGED
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-06-03
|
|
4
|
+
|
|
5
|
+
- [2.17.0] Combine all helpers to ```_helper.js```
|
|
6
|
+
- [2.17.0] ```callHandler()``` now can accept an instance of ```Tools``` as its scope too
|
|
7
|
+
- [2.17.0] Bug fix in ```app.dump()```
|
|
8
|
+
|
|
3
9
|
## 2026-06-01
|
|
4
10
|
|
|
5
11
|
- [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
|
|
File without changes
|