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.
@@ -1,30 +1,32 @@
1
- import Print from '../plugin/print.js'
2
- import Log from '../app/log.js'
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
- buildConfigs,
8
- checkDependencies,
9
- checkNameAliases,
10
- collectHooks,
11
- run
12
- } from './base.js'
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
- orderBy,
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
- * Internal helpers called by Bajo that only used once for bootstrapping. It should remains
95
- * hidden and not to be imported by any program.
96
- *
97
- * @module Helper/Bajo
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 { runAsApplet } from './helper/bajo.js'
5
- import Cache from './app/cache.js'
6
- import Tools from './plugin/tools.js'
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``` but with max 10 depth.
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 - any arguments passed will be displayed on screen. If the last argument is a boolean 'true', app will quit rightaway
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
- for (const arg of args) {
245
- const result = util.inspect(arg, { depth: 10, colors: true })
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 './helper/bajo.js'
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
  }
@@ -1,5 +1,5 @@
1
1
  import os from 'os'
2
- import logLevels from '../../lib/log-levels.js'
2
+ import logLevels from '../lib/log-levels.js'
3
3
  import chalk from 'chalk'
4
4
  import { stripVTControlCharacters } from 'node:util'
5
5
 
package/class/plugin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import lodash from 'lodash'
2
- import Err from './plugin/err.js'
2
+ import Err from './err.js'
3
3
 
4
4
  const { get, isEmpty, cloneDeep, omit, isPlainObject, camelCase } = lodash
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bajo",
3
- "version": "2.15.1",
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.13.1",
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()```
@@ -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
- }
@@ -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