bajo 2.0.2 → 2.2.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.
Files changed (99) hide show
  1. package/.github/FUNDING.yml +0 -0
  2. package/.github/workflows/repo-lockdown.yml +0 -0
  3. package/.jsdoc.conf.json +0 -0
  4. package/.mocharc.json +4 -0
  5. package/LICENSE +0 -0
  6. package/README.md +0 -0
  7. package/class/{misc → app}/log.js +73 -24
  8. package/class/app.js +65 -50
  9. package/class/bajo.js +43 -211
  10. package/class/base.js +25 -22
  11. package/class/helper/bajo.js +67 -60
  12. package/class/helper/base.js +34 -75
  13. package/class/{misc → plugin}/err.js +23 -18
  14. package/class/{misc → plugin}/print.js +7 -16
  15. package/class/plugin/tools.js +42 -0
  16. package/class/plugin.js +58 -54
  17. package/docs/App.html +0 -0
  18. package/docs/Bajo.html +0 -0
  19. package/docs/Base.html +0 -0
  20. package/docs/Err.html +0 -0
  21. package/docs/Log.html +0 -0
  22. package/docs/Plugin.html +0 -0
  23. package/docs/Print.html +0 -0
  24. package/docs/class_app.js.html +0 -0
  25. package/docs/class_bajo.js.html +0 -0
  26. package/docs/class_base.js.html +0 -0
  27. package/docs/class_helper_bajo.js.html +0 -0
  28. package/docs/class_helper_base.js.html +0 -0
  29. package/docs/class_misc_err.js.html +0 -0
  30. package/docs/class_misc_log.js.html +0 -0
  31. package/docs/class_misc_print.js.html +0 -0
  32. package/docs/class_plugin.js.html +0 -0
  33. package/docs/data/search.json +0 -0
  34. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  35. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  36. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  37. package/docs/global.html +0 -0
  38. package/docs/index.html +0 -0
  39. package/docs/index.js.html +0 -0
  40. package/docs/lib_current-loc.js.html +0 -0
  41. package/docs/lib_formats.js.html +0 -0
  42. package/docs/lib_import-module.js.html +0 -0
  43. package/docs/lib_log-levels.js.html +0 -0
  44. package/docs/lib_parse-args-argv.js.html +0 -0
  45. package/docs/lib_parse-env.js.html +0 -0
  46. package/docs/lib_resolve-path.js.html +0 -0
  47. package/docs/lib_shim.js.html +0 -0
  48. package/docs/module-Helper_Bajo.html +0 -0
  49. package/docs/module-Helper_Base.html +0 -0
  50. package/docs/module-Lib.html +0 -0
  51. package/docs/scripts/core.js +476 -477
  52. package/docs/scripts/core.min.js +0 -0
  53. package/docs/scripts/resize.js +36 -36
  54. package/docs/scripts/search.js +105 -105
  55. package/docs/scripts/search.min.js +0 -0
  56. package/docs/scripts/third-party/Apache-License-2.0.txt +0 -0
  57. package/docs/scripts/third-party/fuse.js +1 -1
  58. package/docs/scripts/third-party/hljs-line-num-original.js +282 -285
  59. package/docs/scripts/third-party/hljs-line-num.js +1 -1
  60. package/docs/scripts/third-party/hljs-original.js +1195 -1202
  61. package/docs/scripts/third-party/hljs.js +1 -1
  62. package/docs/scripts/third-party/popper.js +1 -1
  63. package/docs/scripts/third-party/tippy.js +1 -1
  64. package/docs/scripts/third-party/tocbot.js +508 -509
  65. package/docs/scripts/third-party/tocbot.min.js +0 -0
  66. package/docs/static/bitcoin.jpeg +0 -0
  67. package/docs/static/home.md +0 -0
  68. package/docs/static/logo-ecosystem.png +0 -0
  69. package/docs/static/logo.png +0 -0
  70. package/docs/styles/clean-jsdoc-theme-base.css +0 -0
  71. package/docs/styles/clean-jsdoc-theme-dark.css +0 -0
  72. package/docs/styles/clean-jsdoc-theme-light.css +0 -0
  73. package/docs/styles/clean-jsdoc-theme-scrollbar.css +0 -0
  74. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +0 -0
  75. package/docs/styles/clean-jsdoc-theme.min.css +0 -0
  76. package/extend/bajo/intl/en-US.json +11 -5
  77. package/extend/bajo/intl/id.json +11 -5
  78. package/extend/waibuStatic/virtual.json +0 -0
  79. package/index.js +9 -1
  80. package/lib/find-deep.js +24 -0
  81. package/lib/formats.js +0 -0
  82. package/lib/freeze.js +16 -0
  83. package/lib/import-module.js +5 -3
  84. package/lib/index.js +6 -0
  85. package/lib/log-levels.js +0 -0
  86. package/package.json +5 -11
  87. package/test/base.test.js +108 -0
  88. package/wiki/CHANGES.md +63 -0
  89. package/wiki/CONFIG.md +7 -1
  90. package/wiki/CONTRIBUTING.md +0 -0
  91. package/wiki/DEV_GUIDE.md +0 -0
  92. package/wiki/ECOSYSTEM.md +0 -0
  93. package/wiki/GETTING-STARTED.md +1 -1
  94. package/wiki/USER-GUIDE.md +0 -0
  95. package/lib/current-loc.js +0 -33
  96. package/lib/parse-args-argv.js +0 -80
  97. package/lib/parse-env.js +0 -50
  98. package/lib/resolve-path.js +0 -24
  99. package/lib/shim.js +0 -37
@@ -1,7 +1,5 @@
1
- import currentLoc from '../../lib/current-loc.js'
2
- import resolvePath from '../../lib/resolve-path.js'
3
- import Print from '../misc/print.js'
4
- import Log from '../misc/log.js'
1
+ import Print from '../plugin/print.js'
2
+ import Log from '../app/log.js'
5
3
  import omitDeep from 'omit-deep'
6
4
  import os from 'os'
7
5
  import fs from 'fs-extra'
@@ -10,15 +8,14 @@ import {
10
8
  buildConfigs,
11
9
  checkDependencies,
12
10
  checkNameAliases,
13
- attachMethods,
14
11
  collectHooks,
15
12
  run
16
13
  } from './base.js'
14
+ import aneka from 'aneka'
15
+
16
+ const { currentLoc, resolvePath } = aneka
17
17
 
18
18
  const {
19
- reduce,
20
- isNaN,
21
- forOwn,
22
19
  orderBy,
23
20
  isFunction,
24
21
  isPlainObject,
@@ -40,13 +37,23 @@ const omitted = ['spawn', 'cwd', 'name', 'alias', 'applet', 'a', 'plugins']
40
37
 
41
38
  const defConfig = {
42
39
  env: 'dev',
40
+ runtime: {
41
+ noWarning: false
42
+ },
43
43
  log: {
44
44
  timeTaken: false,
45
- dateFormat: 'YYYY-MM-DDTHH:MM:ss.SSS[Z]',
46
- localDate: false,
45
+ dateFormat: 'YYYY-MM-DDTHH:mm:ss.SSS',
46
+ useUtc: false,
47
47
  pretty: false,
48
48
  applet: false,
49
- traceHook: false
49
+ traceHook: false,
50
+ save: false,
51
+ rotation: {
52
+ cycle: 'none', // none, daily, weekly, monthly
53
+ compressOld: true,
54
+ byPlugin: false,
55
+ retain: 5
56
+ }
50
57
  },
51
58
  lang: Intl.DateTimeFormat().resolvedOptions().lang ?? 'en-US',
52
59
  intl: {
@@ -74,7 +81,7 @@ const defConfig = {
74
81
  const defMain = `async function factory (pkgName) {
75
82
  const me = this
76
83
 
77
- return class Main extends this.app.pluginClass.base {
84
+ return class Main extends this.app.baseClass.Base {
78
85
  constructor () {
79
86
  super(pkgName, me.app)
80
87
  this.config = {}
@@ -108,23 +115,24 @@ export async function buildBaseConfig () {
108
115
  this.config = defaultsDeep({}, this.app.envVars._, this.app.argv._)
109
116
  set(this, 'dir.base', this.app.dir)
110
117
  const path = currentLoc(import.meta).dir + '/../..'
111
- set(this, 'dir.pkg', this.resolvePath(path))
118
+ set(this, 'dir.pkg', resolvePath(path))
119
+ if (get(this, 'config.dir.data')) set(this, 'dir.data', this.config.dir.data)
112
120
  if (!get(this, 'dir.data')) set(this, 'dir.data', `${this.dir.base}/data`)
113
- this.dir.data = this.resolvePath(this.dir.data)
114
- if (!fs.existsSync(this.dir.data)) {
115
- console.log('Data directory (%s) doesn\'t exist yet', this.dir.data)
116
- process.exit(1)
117
- }
121
+ this.dir.data = resolvePath(this.dir.data)
118
122
  fs.ensureDirSync(`${this.dir.data}/config`)
119
123
  if (!this.dir.tmp) {
120
- this.dir.tmp = `${this.resolvePath(os.tmpdir())}/${this.ns}`
124
+ this.dir.tmp = `${resolvePath(os.tmpdir())}/${this.ns}`
121
125
  fs.ensureDirSync(this.dir.tmp)
122
126
  }
127
+ this.pkg = await this.getPkgInfo()
123
128
  // collect list of plugins
124
- let pluginPkgs = []
125
- const pluginsFile = `${this.dir.data}/config/.plugins`
126
- if (fs.existsSync(pluginsFile)) {
127
- pluginPkgs = pluginPkgs.concat(filter(map(trim(fs.readFileSync(pluginsFile, 'utf8')).split('\n'), p => trim(p)), b => !isEmpty(b)))
129
+ const mainPkg = await this.getPkgInfo(this.app.dir)
130
+ let pluginPkgs = get(mainPkg, 'bajo.plugins', [])
131
+ if (isEmpty(pluginPkgs)) {
132
+ const pluginsFile = `${this.dir.data}/config/.plugins`
133
+ if (fs.existsSync(pluginsFile)) {
134
+ pluginPkgs = pluginPkgs.concat(filter(map(trim(fs.readFileSync(pluginsFile, 'utf8')).split('\n'), p => trim(p)), b => !isEmpty(b)))
135
+ }
128
136
  }
129
137
  this.app.pluginPkgs = map(filter(without(uniq(pluginPkgs), this.app.mainNs), p => {
130
138
  return p[0] !== '#'
@@ -160,7 +168,10 @@ export async function buildPlugins () {
160
168
  const { default: builder } = await import(resolvePath(factory, true))
161
169
  const ClassDef = await builder.call(this, pkg)
162
170
  const plugin = new ClassDef()
163
- if (!(plugin instanceof this.app.pluginClass.base)) throw this.error('pluginPackageInvalid%s', pkg)
171
+ if (!(plugin instanceof this.app.baseClass.Base)) throw this.error('pluginPackageInvalid%s', pkg)
172
+ plugin.pkg = plugin.getPkgInfo(ns === 'main' ? this.dir.base : dir)
173
+ plugin.alias = ns === 'main' ? this.app.mainNs : get(plugin.pkg, 'bajo.alias', (pkg.slice(0, 5) === 'bajo-' ? pkg.slice(5) : ns).toLowerCase())
174
+ plugin.dependencies = get(plugin.pkg, 'bajo.dependencies', [])
164
175
  this.app.addPlugin(plugin, ClassDef)
165
176
  this.log.trace('- ' + pkg)
166
177
  }
@@ -186,7 +197,6 @@ export async function collectConfigHandlers () {
186
197
  if (isPlainObject(mod)) mod = [mod]
187
198
  this.app.configHandlers = this.app.configHandlers.concat(mod)
188
199
  }
189
- this.app.log = new Log(this.app)
190
200
  }
191
201
 
192
202
  /**
@@ -200,9 +210,11 @@ export async function collectConfigHandlers () {
200
210
  export async function buildExtConfig () {
201
211
  // config merging
202
212
  const { defaultsDeep } = this.app.lib.aneka
213
+ const { parseObject } = this.app.lib
214
+
203
215
  let resp = await this.readAllConfigs(`${this.dir.data}/config/${this.ns}`)
204
- resp = omitDeep(pick(resp, ['log', 'exitHandler', 'env']), omitted)
205
- const envs = this.app.constructor.envs
216
+ resp = omitDeep(pick(resp, ['log', 'exitHandler', 'env', 'runtime']), omitted)
217
+ const envs = this.app.envs
206
218
  this.config = defaultsDeep({}, this.config, resp, defConfig)
207
219
  // language
208
220
  this.config.lang = (this.config.lang ?? '').split('.')[0]
@@ -215,13 +227,16 @@ export async function buildExtConfig () {
215
227
  if (!this.config.log.level) this.config.log.level = this.config.env === 'dev' ? 'debug' : 'info'
216
228
  // misc
217
229
  const obj = this.app.applet ? this.config : pick(this.config, keys(defConfig))
218
- this.config = this.parseObject(obj, { parseValue: true })
230
+ this.config = parseObject(obj, { parseValue: true })
219
231
  const exts = this.app.getConfigFormats()
220
232
  if (this.app.applet) {
221
233
  if (!this.app.pluginPkgs.includes('bajo-cli')) throw this.error('appletNeedsBajoCli')
222
234
  if (!this.config.log.applet) this.config.log.level = 'silent'
223
235
  this.config.exitHandler = false
224
236
  }
237
+ if (this.config.runtime.noWarning) process.removeAllListeners('warning')
238
+ this.app.log = new Log(this.app)
239
+ this.log.trace('dataDir%s', this.dir.data)
225
240
  this.log.debug('configHandlers%s', this.join(exts))
226
241
  }
227
242
 
@@ -231,32 +246,26 @@ export async function buildExtConfig () {
231
246
  * @async
232
247
  */
233
248
  export async function bootOrder () {
249
+ const { freeze } = this.app.lib
250
+ const { isNumber } = this.app.lib._
234
251
  this.log.debug('setupBootOrder')
235
- const order = reduce(this.app.pluginPkgs, (o, k, i) => {
236
- const key = map(k.split(':'), m => trim(m))
237
- if (key[1] && !isNaN(Number(key[1]))) o[key[0]] = Number(key[1])
238
- else o[key[0]] = 10000 + i
239
- return o
240
- }, {})
241
- const norder = {}
242
- for (let n of this.app.pluginPkgs) {
243
- n = map(n.split(':'), m => trim(m))[0]
244
- const dir = n === this.app.mainNs ? (`${this.dir.base}/${this.app.mainNs}`) : this.getModuleDir(n)
245
- if (n !== this.app.mainNs && !fs.existsSync(dir)) throw this.error('packageNotFoundOrNotBajo%s', n)
246
- norder[n] = NaN
247
- try {
248
- norder[n] = Number(trim(await fs.readFile(`${dir}/.bootorder`, 'utf8')))
249
- } catch (err) {}
252
+ let counter = 1000
253
+ const orders = []
254
+ for (const pkg of this.app.pluginPkgs) {
255
+ const item = { pkg }
256
+ const ns = camelCase(pkg)
257
+ const order = get(this.app[ns], 'pkg.bajo.bootorder')
258
+ if (isNumber(order)) item.val = order
259
+ else {
260
+ item.val = counter
261
+ counter++
262
+ }
263
+ orders.push(item)
250
264
  }
251
- const result = []
252
- forOwn(order, (v, k) => {
253
- const item = { k, v: isNaN(norder[k]) ? v : norder[k] }
254
- result.push(item)
255
- })
256
- this.app.pluginPkgs = map(orderBy(result, ['v']), 'k')
257
- this.log.debug('runInEnv%s', this.t(this.app.constructor.envs[this.config.env]))
265
+ this.app.pluginPkgs = map(orderBy(orders, ['val']), 'pkg')
266
+ this.log.debug('runInEnv%s', this.t(this.app.envs[this.config.env]))
258
267
  // misc
259
- this.freeze(this.config)
268
+ freeze(this.config)
260
269
  }
261
270
 
262
271
  /**
@@ -265,9 +274,8 @@ export async function bootOrder () {
265
274
  * 1. {@link module:Helper/Base.buildConfigs|build configs}
266
275
  * 2. {@link module:Helper/Base.checkNameAliases|ensure names & aliases uniqueness}
267
276
  * 3. {@link module:Helper/Base.checkDependencies|ensure dependencies are met}
268
- * 4. {@link module:Helper/Base.attachMethods|build and attach dynamic methods}
269
- * 5. {@link module:Helper/Base.collectHooks|collect hooks}
270
- * 6. {@link module:Helper/Base.run|run plugins}
277
+ * 4. {@link module:Helper/Base.collectHooks|collect hooks}
278
+ * 5. {@link module:Helper/Base.run|run plugins}
271
279
  *
272
280
  * @async
273
281
  */
@@ -275,7 +283,6 @@ export async function bootPlugins () {
275
283
  await buildConfigs.call(this.app)
276
284
  await checkNameAliases.call(this.app)
277
285
  await checkDependencies.call(this.app)
278
- await attachMethods.call(this.app)
279
286
  await collectHooks.call(this.app)
280
287
  await run.call(this.app)
281
288
  }
@@ -291,11 +298,12 @@ export async function exitHandler () {
291
298
  async function exit (signal) {
292
299
  const { eachPlugins } = this
293
300
  if (signal) this.log.warn('signalReceived%s', signal)
301
+ const me = this
294
302
  await eachPlugins(async function ({ ns }) {
295
303
  try {
296
304
  await this.exit()
297
305
  } catch (err) {}
298
- this.log.trace('exited')
306
+ me.log.trace('exited%s', this.ns)
299
307
  })
300
308
  this.log.debug('appShutdown')
301
309
  process.exit(0)
@@ -351,8 +359,7 @@ export async function exitHandler () {
351
359
  export async function runAsApplet () {
352
360
  const { isString, map, find } = this.app.lib._
353
361
  await this.eachPlugins(async function ({ file }) {
354
- const { ns } = this
355
- const { alias } = this.constructor
362
+ const { ns, alias } = this
356
363
  this.app.applets.push({ ns, file, alias })
357
364
  }, { glob: 'applet.js', prefix: 'bajoCli' })
358
365
 
@@ -380,7 +387,7 @@ export async function runAsApplet () {
380
387
  * @see {@tutorial hook}
381
388
  * @see module:Helper/Bajo.runAsApplet
382
389
  */
383
- await this.runHook(`${this.app[applet.ns]}:beforeAppletRun`, ...this.app.args)
390
+ await this.runHook(`${applet.ns}:beforeAppletRun`, ...this.app.args)
384
391
  await this.app.bajoCli.runApplet(applet, path, ...this.app.args)
385
392
  /**
386
393
  * Run after applet is run. ```[ns]``` is applet's namespace
@@ -391,5 +398,5 @@ export async function runAsApplet () {
391
398
  * @see {@tutorial hook}
392
399
  * @see module:Helper/Bajo.runAsApplet
393
400
  */
394
- await this.runHook(`${this.app[applet.ns]}:afterAppletRun`, ...this.app.args)
401
+ await this.runHook(`${applet.ns}:afterAppletRun`, ...this.app.args)
395
402
  }
@@ -1,11 +1,8 @@
1
1
  import semver from 'semver'
2
2
  import lodash from 'lodash'
3
- import Print from '../misc/print.js'
4
- import path from 'path'
5
- import resolvePath from '../../lib/resolve-path.js'
3
+ import Print from '../plugin/print.js'
6
4
 
7
5
  const {
8
- isFunction,
9
6
  merge,
10
7
  forOwn,
11
8
  groupBy,
@@ -27,41 +24,6 @@ const {
27
24
  * @module Helper/Base
28
25
  */
29
26
 
30
- /**
31
- * Scan plugins ```method``` directories, and turn + attach its found files as methods dynamically.
32
- *
33
- * @async
34
- */
35
- export async function attachMethods () {
36
- const { fastGlob } = this.lib
37
-
38
- async function createMethod (dir) {
39
- dir = resolvePath(dir)
40
- const files = await fastGlob([`!${dir}/**/_*.{js,json}`, `${dir}/**/*.{js,json}`])
41
- for (const f of files) {
42
- const ext = path.extname(f)
43
- const base = f.replace(dir, '').slice(0, -ext.length)
44
- const name = camelCase(base)
45
- let mod
46
- if (ext === '.json') mod = this.app.bajo.readJson(f)
47
- else mod = await this.app.bajo.importModule(f)
48
- if (isFunction(mod)) mod = mod.bind(this)
49
- this[name] = mod
50
- }
51
- return files.length
52
- }
53
-
54
- const { eachPlugins } = this.bajo
55
- const me = this // the app
56
- me.bajo.log.debug('attachMethods')
57
- await eachPlugins(async function () {
58
- const { ns, pkgName } = this
59
- const dir = ns === me.mainNs ? (`${me.bajo.dir.base}/${me.mainNs}`) : me.bajo.getModuleDir(pkgName)
60
- const num = await createMethod.call(me[ns], `${dir}/method`, pkgName)
61
- me.bajo.log.trace('- %s (%d)', ns, num)
62
- })
63
- }
64
-
65
27
  /**
66
28
  * Build configurations
67
29
  *
@@ -82,18 +44,17 @@ export async function buildConfigs () {
82
44
  * @async
83
45
  */
84
46
  export async function checkNameAliases () {
85
- const { eachPlugins } = this.bajo
86
47
  this.bajo.log.debug('checkAliasNameClash')
87
48
  const refs = []
88
- await eachPlugins(async function () {
89
- const { ns, pkgName } = this
90
- const { alias } = this.constructor
49
+ for (const pkg of this.bajo.app.pluginPkgs) {
50
+ const plugin = this.bajo.app[camelCase(pkg)]
51
+ const { ns, alias } = plugin
91
52
  let item = find(refs, { ns })
92
- if (item) throw this.error('pluginNameClash%s%s%s%s', ns, pkgName, item.ns, item.pkgName, { code: 'BAJO_NAME_CLASH' })
53
+ if (item) throw this.error('pluginNameClash%s%s%s%s', ns, pkg, item.ns, item.pkg, { code: 'BAJO_NAME_CLASH' })
93
54
  item = find(refs, { alias })
94
- if (item) throw this.error('pluginNameClash%s%s%s%s', alias, pkgName, item.alias, item.pkgName, { code: 'BAJO_ALIAS_CLASH' })
95
- refs.push({ ns, alias, pkgName })
96
- })
55
+ if (item) throw this.error('pluginNameClash%s%s%s%s', alias, pkg, item.alias, item.pkg, { code: 'BAJO_ALIAS_CLASH' })
56
+ refs.push({ ns, alias, pkg })
57
+ }
97
58
  }
98
59
 
99
60
  /**
@@ -102,11 +63,12 @@ export async function checkNameAliases () {
102
63
  * @async
103
64
  */
104
65
  export async function checkDependencies () {
105
- async function runner () {
106
- const { ns, pkgName } = this
107
- const { join } = this.app.bajo
108
- this.app.bajo.log.trace('- %s', ns)
109
- const { dependencies } = this.app.pluginClass[this.ns]
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)
110
72
  const odep = reduce(dependencies, (o, k) => {
111
73
  const item = map(k.split('@'), m => trim(m))
112
74
  if (k[0] === '@') o['@' + item[1]] = item[2]
@@ -115,25 +77,19 @@ export async function checkDependencies () {
115
77
  }, {})
116
78
  const deps = keys(odep)
117
79
  if (deps.length > 0) {
118
- if (intersection(this.app.pluginPkgs, deps).length !== deps.length) {
119
- throw this.error('dependencyUnfulfilled%s%s', pkgName, join(deps), { code: 'BAJO_DEPENDENCY' })
80
+ if (intersection(this.bajo.app.pluginPkgs, deps).length !== deps.length) {
81
+ throw this.error('dependencyUnfulfilled%s%s', pkg, join(deps), { code: 'BAJO_DEPENDENCY' })
120
82
  }
121
83
  each(deps, d => {
122
84
  if (!odep[d]) return
123
- const ver = get(this.app[camelCase(d)], 'config.pkg.version')
85
+ const ver = get(this.bajo.app[camelCase(d)], 'pkg.version')
124
86
  if (!ver) return
125
87
  if (!semver.satisfies(ver, odep[d])) {
126
- throw this.error('semverCheckFailed%s%s', pkgName, `${d}@${odep[d]}`, { code: 'BAJO_DEPENDENCY_SEMVER' })
88
+ throw this.error('semverCheckFailed%s%s', pkg, `${d}@${odep[d]}`, { code: 'BAJO_DEPENDENCY_SEMVER' })
127
89
  }
128
90
  })
129
91
  }
130
92
  }
131
-
132
- const { eachPlugins } = this.bajo
133
- this.bajo.log.debug('checkDeps')
134
- await eachPlugins(async function () {
135
- await runner.call(this)
136
- })
137
93
  }
138
94
 
139
95
  /**
@@ -146,25 +102,26 @@ export async function collectHooks () {
146
102
  const { eachPlugins, runHook, isLogInRange, importModule, breakNsPathFromFile } = this.bajo
147
103
  const me = this
148
104
  me.bajo.hooks = this.bajo.hooks ?? []
149
- me.bajo.log.debug('collectHooks')
105
+ me.bajo.log.trace('collecting%s', this.t('hooks'))
150
106
  // collects
151
107
  await eachPlugins(async function ({ dir, file }) {
152
- const { ns } = this
153
- const { fullNs, path } = breakNsPathFromFile({ file, dir, baseNs: ns, suffix: '/hook/' })
108
+ const { ns: baseNs } = this
109
+ const { ns, subNs, path } = breakNsPathFromFile({ file, dir, baseNs, suffix: '/hook/' })
154
110
  const mod = await importModule(file, { asHandler: true })
155
111
  if (!mod) return undefined
156
- merge(mod, { ns: fullNs, path, src: ns })
112
+ merge(mod, { ns, subNs, path, src: baseNs })
157
113
  me.bajo.hooks.push(mod)
158
114
  }, { glob: 'hook/**/*.js', prefix: me.bajo.ns })
159
115
  // for log trace purpose only
160
- if (!isLogInRange('trace')) return
161
- const items = groupBy(me.bajo.hooks, 'ns')
162
- forOwn(items, (v, k) => {
163
- const hooks = groupBy(v, 'path')
164
- forOwn(hooks, (v1, k1) => {
165
- me.bajo.log.trace('- %s:%s (%d)', k, k1, v1.length)
116
+ if (isLogInRange('trace')) {
117
+ const items = groupBy(me.bajo.hooks, item => item.ns + (item.subNs ? `.${item.subNs}` : ''))
118
+ forOwn(items, (v, k) => {
119
+ const hooks = groupBy(v, 'path')
120
+ forOwn(hooks, (v1, k1) => {
121
+ me.bajo.log.trace('- %s:%s (%d)', k, k1, v1.length)
122
+ })
166
123
  })
167
- })
124
+ }
168
125
 
169
126
  /**
170
127
  * Run after hooks are collected
@@ -176,6 +133,7 @@ export async function collectHooks () {
176
133
  * @see module:Helper/Base.collectHooks
177
134
  */
178
135
  await runHook('bajo:afterCollectHooks', this.bajo.hooks)
136
+ me.bajo.log.debug('collected%s%d', this.t('hooks'), me.bajo.hooks.length)
179
137
  }
180
138
 
181
139
  /**
@@ -190,10 +148,9 @@ export async function collectHooks () {
190
148
  export async function run () {
191
149
  const me = this
192
150
  const { runHook, eachPlugins, join } = me.bajo
193
- const { freeze } = me.bajo
151
+ const { freeze } = me.lib
194
152
  const methods = ['init']
195
153
  if (!me.applet) methods.push('start')
196
- me.bajo.log.debug('loadedPlugins%s', join(map(me.bajo.app.pluginPkgs, b => camelCase(b))))
197
154
  for (const method of methods) {
198
155
  /**
199
156
  * Run before all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
@@ -239,5 +196,7 @@ export async function run () {
239
196
  * @see module:Helper/Base.run
240
197
  */
241
198
  await runHook(`bajo:${camelCase(`after all ${method}`)}`)
199
+ if (me.bajo.config.log.level === 'trace') me.bajo.log.trace('loadedPlugins%s', join(map(me.bajo.app.pluginPkgs, b => camelCase(b))))
200
+ else me.bajo.log.debug('loadedPlugins%s', me.bajo.app.pluginPkgs.length)
242
201
  }
243
202
  }
@@ -1,4 +1,6 @@
1
1
  import lodash from 'lodash'
2
+ import Tools from './tools.js'
3
+
2
4
  const { isPlainObject, each, isArray, get, isEmpty, merge } = lodash
3
5
 
4
6
  Error.stackTraceLimit = 15
@@ -14,24 +16,14 @@ Error.stackTraceLimit = 15
14
16
  * if (notfound) throw this.error('Sorry, item is nowhere to be found!')
15
17
  * ```
16
18
  */
17
- class Err {
19
+ class Err extends Tools {
18
20
  /**
19
21
  * @param {Plugin} plugin - Plugin instance
20
22
  * @param {string} msg - Error message
21
23
  * @param {...any} [args] - Variables to interpolate with error message. Payload object can be pushed at the very last argument
22
24
  */
23
25
  constructor (plugin, msg, ...args) {
24
- /**
25
- * Attached plugin
26
- * @type {Plugin}
27
- */
28
- this.plugin = plugin
29
-
30
- /**
31
- * The app instance
32
- * @type {App}
33
- */
34
- this.app = plugin.app
26
+ super(plugin)
35
27
 
36
28
  /**
37
29
  * Error payload extracted from the last arguments
@@ -50,7 +42,6 @@ class Err {
50
42
  * @type {string}
51
43
  */
52
44
  this.message = this.payload.noTrans ? msg : this.plugin.t(msg, ...args)
53
- this.write()
54
45
  }
55
46
 
56
47
  /**
@@ -69,10 +60,12 @@ class Err {
69
60
  stacks.splice(1, 1)
70
61
  err.stack = stacks.join('\n')
71
62
  const values = {}
63
+ let detailsMessage
72
64
  for (const key in this.payload) {
73
65
  const value = this.payload[key]
74
66
  if (key === 'details' && isArray(value)) {
75
- const result = this.formatErrorDetails(value)
67
+ const { result, detailsMessage: dm } = this.formatErrorDetails(value)
68
+ if (!isEmpty(dm)) detailsMessage = dm
76
69
  if (result) merge(values, result)
77
70
  }
78
71
  err[key] = value
@@ -80,6 +73,7 @@ class Err {
80
73
  if (!isEmpty(values)) err.values = values
81
74
  err.ns = this.plugin.ns
82
75
  err.orgMessage = this.orgMessage
76
+ if (detailsMessage) err.detailsMessage = detailsMessage
83
77
  return err
84
78
  }
85
79
 
@@ -91,7 +85,7 @@ class Err {
91
85
  fatal = () => {
92
86
  const err = this.write()
93
87
  console.error(err)
94
- this.app.exit()
88
+ this.app.exit(true)
95
89
  }
96
90
 
97
91
  /**
@@ -102,9 +96,10 @@ class Err {
102
96
  * @returns {Object}
103
97
  */
104
98
  formatErrorDetails = (value) => {
105
- const { isString } = this.app.lib._
99
+ const { isString, last } = this.app.lib._
106
100
  const result = {}
107
101
  const me = this
102
+ const detailsMessage = []
108
103
  each(value, (v, i) => {
109
104
  if (isString(v)) v = { error: v }
110
105
  if (!v.context) return undefined
@@ -112,14 +107,24 @@ class Err {
112
107
  if (v.type === 'any.only') v.context.ref = get(v, 'context.valids', []).join(', ')
113
108
  const field = get(v, 'context.key')
114
109
  const val = get(v, 'context.value')
110
+ let error = me.plugin.t(`validation.${v.type}`, v.context ?? {}, {})
111
+ if (error.includes(' ref:')) {
112
+ const item = last(error.split(' '))
113
+ const [, rfield] = item.split(':')
114
+ error = error.replace(item, `'${me.plugin.t('field.' + rfield)}'`)
115
+ }
115
116
  value[i] = {
116
117
  field,
117
- error: me.plugin.t(`validation.${v.type}`, v.context ?? {}, {}),
118
+ error,
118
119
  value: val,
119
120
  ext: { type: v.type, context: v.context }
120
121
  }
122
+ detailsMessage.push(me.plugin.t('fieldError%s%s', field, value[i].error))
121
123
  })
122
- return result
124
+ return {
125
+ result,
126
+ detailsMessage: detailsMessage.length > 0 ? (me.plugin.t('error') + ': ' + detailsMessage.join(', ')) : ''
127
+ }
123
128
  }
124
129
  }
125
130
 
@@ -1,7 +1,8 @@
1
1
  import ora from 'ora'
2
2
  import lodash from 'lodash'
3
- import aneka from 'aneka'
4
- const { defaultsDeep } = aneka
3
+ import defaultsDeep from 'aneka/src/defaults-deep.js'
4
+ import secToHms from 'aneka/src/sec-to-hms.js'
5
+ import Tools from './tools.js'
5
6
 
6
7
  const { isPlainObject } = lodash
7
8
 
@@ -22,29 +23,19 @@ const { isPlainObject } = lodash
22
23
  *
23
24
  * @class
24
25
  */
25
- class Print {
26
+ class Print extends Tools {
26
27
  /**
27
28
  * @param {Plugin} plugin - Plugin instance
28
29
  * @param {TPrintOptions} [options={}] - Options object
29
30
  */
30
31
  constructor (plugin, options = {}) {
32
+ super(plugin)
33
+
31
34
  /**
32
35
  * Options object
33
36
  * @type {TPrintOptions}
34
37
  */
35
38
  this.options = options
36
-
37
- /**
38
- * Attached plugin
39
- * @type {Plugin}
40
- */
41
- this.plugin = plugin
42
-
43
- /**
44
- * The app instance
45
- * @type {App}
46
- */
47
- this.app = plugin.app
48
39
  if (this.app.applet) {
49
40
  if (this.app.bajo.config.counter) this.options.showCounter = true
50
41
  if (this.app.bajo.config.datetime) this.options.showDatetime = true
@@ -122,7 +113,7 @@ class Print {
122
113
  getElapsed = (unit = 'hms') => {
123
114
  const u = unit === 'hms' ? 'second' : unit
124
115
  const elapsed = this.app.lib.dayjs().diff(this.startTime, u)
125
- return unit === 'hms' ? this.app.lib.aneka.secToHms(elapsed) : elapsed
116
+ return unit === 'hms' ? secToHms(elapsed) : elapsed
126
117
  }
127
118
 
128
119
  /**
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Base tools class
3
+ *
4
+ * @class
5
+ */
6
+
7
+ class Tools {
8
+ constructor (plugin) {
9
+ /**
10
+ * Attached plugin
11
+ * @type {Plugin}
12
+ */
13
+ this.plugin = plugin
14
+
15
+ /**
16
+ * The app instance
17
+ * @type {App}
18
+ */
19
+ this.app = plugin.app
20
+ }
21
+
22
+ /**
23
+ * Force bind methods to self (```this```)
24
+ *
25
+ * @param {string[]} names - Method's names
26
+ */
27
+ selfBind (names) {
28
+ for (const name of names) {
29
+ this[name] = this[name].bind(this)
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Dispose internal references
35
+ */
36
+ dispose () {
37
+ this.app = null
38
+ this.plugin = null
39
+ }
40
+ }
41
+
42
+ export default Tools