bajo 2.15.1 → 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 +51 -10
- package/class/bajo.js +3 -2
- package/class/{app/log.js → log.js} +1 -1
- package/class/plugin.js +1 -1
- package/package.json +2 -2
- package/wiki/CHANGES.md +12 -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
|
-
|
|
8
|
-
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'
|
|
7
|
+
import { fileURLToPath } from 'url'
|
|
9
8
|
|
|
10
9
|
const { camelCase, isPlainObject, get, reverse, map, last, without, set } = lib._
|
|
11
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
|
|
@@ -197,6 +215,11 @@ class App {
|
|
|
197
215
|
*/
|
|
198
216
|
this.envVars = {}
|
|
199
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Placeholder for boxen that will get imported from ```bajoCli``` later during boot process.
|
|
220
|
+
*/
|
|
221
|
+
this.boxen = null
|
|
222
|
+
|
|
200
223
|
this.cache = new Cache(this)
|
|
201
224
|
|
|
202
225
|
if (!options.cwd) options.cwd = process.cwd()
|
|
@@ -233,19 +256,36 @@ class App {
|
|
|
233
256
|
}
|
|
234
257
|
|
|
235
258
|
/**
|
|
236
|
-
* 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.
|
|
237
269
|
*
|
|
238
270
|
* @method
|
|
239
|
-
* @param {...any} args -
|
|
271
|
+
* @param {...any} args - Variables to dump
|
|
240
272
|
*/
|
|
241
273
|
dump = (...args) => {
|
|
274
|
+
let caller = getCallerFilename()
|
|
275
|
+
caller = caller ? fileURLToPath(caller) : 'Unavailable'
|
|
242
276
|
const terminate = last(args) === true
|
|
243
277
|
if (terminate) args.pop()
|
|
244
|
-
|
|
245
|
-
|
|
278
|
+
const value = args.length === 1 ? args[0] : args
|
|
279
|
+
const options = { ...this.bajo.config.dump }
|
|
280
|
+
if (this.boxen) {
|
|
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))
|
|
285
|
+
} else {
|
|
286
|
+
const result = util.inspect(options.caller ? [caller, value] : value, options)
|
|
246
287
|
console.log(result)
|
|
247
288
|
}
|
|
248
|
-
// if (terminate) process.kill(process.pid, 'SIGINT')
|
|
249
289
|
if (terminate) process.exit('1')
|
|
250
290
|
}
|
|
251
291
|
|
|
@@ -285,6 +325,7 @@ class App {
|
|
|
285
325
|
this.applet = this.envVars._.applet ?? this.argv._.applet
|
|
286
326
|
await this.bajo.runHook('bajo:beforeBoot')
|
|
287
327
|
await this.bajo.init()
|
|
328
|
+
if (this.bajoCli) this.boxen = await this.bajo.importPkg('bajoCli:boxen')
|
|
288
329
|
// cache
|
|
289
330
|
this.cache.purge()
|
|
290
331
|
setInterval(this.cache.purge, this.bajo.config.cache.purgeIntvDur)
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bajo",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.17.0",
|
|
4
4
|
"description": "The ultimate framework for whipping up massive apps in no time",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"homepage": "https://github.com/ardhi/bajo#readme",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"add-filename-increment": "^1.0.0",
|
|
29
|
-
"aneka": "^0.
|
|
29
|
+
"aneka": "^0.14.0",
|
|
30
30
|
"chalk": "^5.6.0",
|
|
31
31
|
"dayjs": "^1.11.13",
|
|
32
32
|
"deep-freeze-strict": "^1.1.1",
|
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
9
|
+
## 2026-06-01
|
|
10
|
+
|
|
11
|
+
- [2.16.0] Upgrade ```aneka@0.14.0```
|
|
12
|
+
- [2.16.0] Adding caller filename to the ```app.dump()```
|
|
13
|
+
- [2.16.0] Formatting ```app.dump()``` to be more catchy when ```bajoCli``` is loaded
|
|
14
|
+
|
|
3
15
|
## 2026-05-30
|
|
4
16
|
|
|
5
17
|
- [2.15.1] Bug fix in ```app.boot()```
|
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
|