bajo 2.0.1 → 2.0.2

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 (101) hide show
  1. package/.github/FUNDING.yml +13 -0
  2. package/.github/workflows/repo-lockdown.yml +24 -0
  3. package/.jsdoc.conf.json +7 -6
  4. package/LICENSE +1 -1
  5. package/README.md +50 -18
  6. package/class/app.js +376 -54
  7. package/class/bajo.js +228 -149
  8. package/class/base.js +106 -0
  9. package/class/helper/bajo.js +135 -84
  10. package/class/helper/{plugin.js → base.js} +96 -22
  11. package/class/{base → misc}/err.js +44 -14
  12. package/class/misc/log.js +212 -0
  13. package/class/misc/print.js +264 -0
  14. package/class/plugin.js +120 -68
  15. package/docs/App.html +23 -1
  16. package/docs/Bajo.html +2 -9
  17. package/docs/Base.html +3 -0
  18. package/docs/Err.html +3 -1
  19. package/docs/Log.html +5 -1
  20. package/docs/Plugin.html +1 -1
  21. package/docs/Print.html +1 -1
  22. package/docs/class_app.js.html +378 -56
  23. package/docs/class_bajo.js.html +230 -151
  24. package/docs/class_base.js.html +109 -0
  25. package/docs/class_helper_bajo.js.html +138 -87
  26. package/docs/class_helper_base.js.html +246 -0
  27. package/docs/class_misc_err.js.html +129 -0
  28. package/docs/class_misc_log.js.html +210 -0
  29. package/docs/class_misc_print.js.html +267 -0
  30. package/docs/class_plugin.js.html +122 -70
  31. package/docs/data/search.json +1 -1
  32. package/docs/global.html +4 -1
  33. package/docs/index.html +2 -2
  34. package/docs/index.js.html +35 -0
  35. package/docs/lib_current-loc.js.html +36 -0
  36. package/docs/lib_formats.js.html +8 -8
  37. package/docs/lib_import-module.js.html +59 -0
  38. package/docs/lib_log-levels.js.html +17 -7
  39. package/docs/lib_parse-args-argv.js.html +83 -0
  40. package/docs/lib_parse-env.js.html +53 -0
  41. package/docs/lib_resolve-path.js.html +3 -6
  42. package/docs/lib_shim.js.html +8 -3
  43. package/docs/module-Helper_Bajo.html +3 -0
  44. package/docs/module-Helper_Base.html +3 -0
  45. package/docs/module-Lib.html +15 -0
  46. package/docs/static/home.md +32 -0
  47. package/docs/static/logo-ecosystem.png +0 -0
  48. package/docs/static/logo.png +0 -0
  49. package/extend/bajo/intl/en-US.json +8 -2
  50. package/extend/bajo/intl/id.json +8 -2
  51. package/index.js +22 -2
  52. package/lib/current-loc.js +24 -2
  53. package/lib/formats.js +6 -6
  54. package/lib/import-module.js +29 -0
  55. package/lib/log-levels.js +15 -5
  56. package/lib/parse-args-argv.js +20 -12
  57. package/lib/parse-env.js +18 -7
  58. package/lib/resolve-path.js +1 -4
  59. package/lib/shim.js +6 -1
  60. package/package.json +4 -7
  61. package/wiki/CONFIG.md +30 -0
  62. package/wiki/CONTRIBUTING.md +5 -0
  63. package/wiki/DEV_GUIDE.md +3 -0
  64. package/wiki/ECOSYSTEM.md +93 -0
  65. package/wiki/GETTING-STARTED.md +356 -0
  66. package/wiki/USER-GUIDE.md +256 -0
  67. package/class/base/log.js +0 -205
  68. package/class/base/plugin.js +0 -168
  69. package/class/base/print.js +0 -272
  70. package/docs/BasePlugin.html +0 -5
  71. package/docs/class_base_err.js.html +0 -99
  72. package/docs/class_base_log.js.html +0 -208
  73. package/docs/class_base_plugin.js.html +0 -180
  74. package/docs/class_base_print.js.html +0 -275
  75. package/docs/class_helper_plugin.js.html +0 -172
  76. package/docs/lib_create-method.js.html +0 -42
  77. package/docs/module-class_helper_bajo.html +0 -3
  78. package/docs/module-class_helper_plugin.html +0 -3
  79. package/docs/module-lib_create-method.html +0 -3
  80. package/docs/module-lib_formats.html +0 -3
  81. package/docs/module-lib_log-levels.html +0 -3
  82. package/docs/module-lib_resolve-path.html +0 -3
  83. package/docs/module-lib_shim.html +0 -3
  84. package/docs/tutorial-contribution.html +0 -3
  85. package/docs/tutorial-ecosystem.html +0 -3
  86. package/docs/tutorial-getting-started.html +0 -13
  87. package/docs/tutorial-plugin-dev.html +0 -3
  88. package/docs/tutorial-user-guide.html +0 -3
  89. package/lib/create-method.js +0 -39
  90. package/lib/dayjs.js +0 -8
  91. package/lib/omitted-plugin-keys.js +0 -3
  92. package/lib/read-all-configs.js +0 -19
  93. package/misc-docs/.hook.md +0 -11
  94. package/misc-docs/bitcoin.jpeg +0 -0
  95. package/misc-docs/contribution.md +0 -20
  96. package/misc-docs/ecosystem.md +0 -94
  97. package/misc-docs/getting-started.md +0 -142
  98. package/misc-docs/plugin-dev.md +0 -0
  99. package/misc-docs/toc.json +0 -17
  100. package/misc-docs/user-guide.md +0 -1
  101. /package/docs/{bitcoin.jpeg → static/bitcoin.jpeg} +0 -0
@@ -1,8 +1,11 @@
1
- import createMethod from '../../lib/create-method.js'
2
1
  import semver from 'semver'
3
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'
4
6
 
5
7
  const {
8
+ isFunction,
6
9
  merge,
7
10
  forOwn,
8
11
  groupBy,
@@ -18,7 +21,10 @@ const {
18
21
  } = lodash
19
22
 
20
23
  /**
21
- * @module
24
+ * Internal helpers called by Bajo & plugins that only used once for bootstrapping purpose.
25
+ * It should remains hidden and not to be imported by any program.
26
+ *
27
+ * @module Helper/Base
22
28
  */
23
29
 
24
30
  /**
@@ -27,12 +33,30 @@ const {
27
33
  * @async
28
34
  */
29
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
+
30
54
  const { eachPlugins } = this.bajo
31
55
  const me = this // the app
32
56
  me.bajo.log.debug('attachMethods')
33
57
  await eachPlugins(async function () {
34
- const { name: ns, pkgName } = this
35
- const dir = ns === me.bajo.mainNs ? (`${me.bajo.dir.base}/${me.bajo.mainNs}`) : me.bajo.getModuleDir(pkgName)
58
+ const { ns, pkgName } = this
59
+ const dir = ns === me.mainNs ? (`${me.bajo.dir.base}/${me.mainNs}`) : me.bajo.getModuleDir(pkgName)
36
60
  const num = await createMethod.call(me[ns], `${dir}/method`, pkgName)
37
61
  me.bajo.log.trace('- %s (%d)', ns, num)
38
62
  })
@@ -45,11 +69,10 @@ export async function attachMethods () {
45
69
  */
46
70
  export async function buildConfigs () {
47
71
  this.bajo.log.debug('readConfigs')
48
- for (const pkg of this.bajo.pluginPkgs) {
49
- const plugin = this[camelCase(pkg)]
50
- await plugin.loadConfig()
51
- plugin.initPrint()
52
- plugin.initLog()
72
+ for (const ns of this.getAllNs()) {
73
+ await this[ns].loadConfig()
74
+ this[ns].print = new Print(this[ns])
75
+ this.loadIntl(ns)
53
76
  }
54
77
  }
55
78
 
@@ -63,7 +86,8 @@ export async function checkNameAliases () {
63
86
  this.bajo.log.debug('checkAliasNameClash')
64
87
  const refs = []
65
88
  await eachPlugins(async function () {
66
- const { name: ns, pkgName, alias } = this
89
+ const { ns, pkgName } = this
90
+ const { alias } = this.constructor
67
91
  let item = find(refs, { ns })
68
92
  if (item) throw this.error('pluginNameClash%s%s%s%s', ns, pkgName, item.ns, item.pkgName, { code: 'BAJO_NAME_CLASH' })
69
93
  item = find(refs, { alias })
@@ -79,10 +103,11 @@ export async function checkNameAliases () {
79
103
  */
80
104
  export async function checkDependencies () {
81
105
  async function runner () {
82
- const { name: ns, pkgName } = this
106
+ const { ns, pkgName } = this
83
107
  const { join } = this.app.bajo
84
108
  this.app.bajo.log.trace('- %s', ns)
85
- const odep = reduce(this.dependencies, (o, k) => {
109
+ const { dependencies } = this.app.pluginClass[this.ns]
110
+ const odep = reduce(dependencies, (o, k) => {
86
111
  const item = map(k.split('@'), m => trim(m))
87
112
  if (k[0] === '@') o['@' + item[1]] = item[2]
88
113
  else o[item[0]] = item[1]
@@ -90,7 +115,7 @@ export async function checkDependencies () {
90
115
  }, {})
91
116
  const deps = keys(odep)
92
117
  if (deps.length > 0) {
93
- if (intersection(this.app.bajo.pluginPkgs, deps).length !== deps.length) {
118
+ if (intersection(this.app.pluginPkgs, deps).length !== deps.length) {
94
119
  throw this.error('dependencyUnfulfilled%s%s', pkgName, join(deps), { code: 'BAJO_DEPENDENCY' })
95
120
  }
96
121
  each(deps, d => {
@@ -115,6 +140,7 @@ export async function checkDependencies () {
115
140
  * Collect and build hooks and push them to the bajo's hook system
116
141
  *
117
142
  * @async
143
+ * @fires bajo:afterCollectHooks
118
144
  */
119
145
  export async function collectHooks () {
120
146
  const { eachPlugins, runHook, isLogInRange, importModule, breakNsPathFromFile } = this.bajo
@@ -123,13 +149,13 @@ export async function collectHooks () {
123
149
  me.bajo.log.debug('collectHooks')
124
150
  // collects
125
151
  await eachPlugins(async function ({ dir, file }) {
126
- const { name: ns } = this
152
+ const { ns } = this
127
153
  const { fullNs, path } = breakNsPathFromFile({ file, dir, baseNs: ns, suffix: '/hook/' })
128
154
  const mod = await importModule(file, { asHandler: true })
129
155
  if (!mod) return undefined
130
156
  merge(mod, { ns: fullNs, path, src: ns })
131
157
  me.bajo.hooks.push(mod)
132
- }, { glob: 'hook/**/*.js', prefix: me.bajo.name })
158
+ }, { glob: 'hook/**/*.js', prefix: me.bajo.ns })
133
159
  // for log trace purpose only
134
160
  if (!isLogInRange('trace')) return
135
161
  const items = groupBy(me.bajo.hooks, 'ns')
@@ -139,31 +165,79 @@ export async function collectHooks () {
139
165
  me.bajo.log.trace('- %s:%s (%d)', k, k1, v1.length)
140
166
  })
141
167
  })
142
- // run handler
143
- await runHook('bajo:afterCollectHooks')
168
+
169
+ /**
170
+ * Run after hooks are collected
171
+ *
172
+ * @global
173
+ * @event bajo:afterCollectHooks
174
+ * @param {Object[]} hooks - Array of hook objects
175
+ * @see {@tutorial hook}
176
+ * @see module:Helper/Base.collectHooks
177
+ */
178
+ await runHook('bajo:afterCollectHooks', this.bajo.hooks)
144
179
  }
145
180
 
146
181
  /**
147
182
  * Finally, run all plugins
148
183
  *
149
184
  * @async
185
+ * @fires bajo:beforeAll{method}
186
+ * @fires {ns}:before{method}
187
+ * @fires {ns}:after{method}
188
+ * @fires bajo:afterAll{method}
150
189
  */
151
190
  export async function run () {
152
191
  const me = this
153
192
  const { runHook, eachPlugins, join } = me.bajo
154
193
  const { freeze } = me.bajo
155
194
  const methods = ['init']
156
- if (!me.bajo.applet) methods.push('start')
195
+ if (!me.applet) methods.push('start')
196
+ me.bajo.log.debug('loadedPlugins%s', join(map(me.bajo.app.pluginPkgs, b => camelCase(b))))
157
197
  for (const method of methods) {
158
- await runHook(`bajo:${camelCase(`before ${method} all plugins`)}`)
198
+ /**
199
+ * Run before all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
200
+ *
201
+ * @global
202
+ * @event bajo:beforeAll{method}
203
+ * @param {string} method - Accepted methods: ```Init```, ```Start```
204
+ * @see module:Helper/Base.run
205
+ */
206
+ await runHook(`bajo:${camelCase(`before all ${method}`)}`)
159
207
  await eachPlugins(async function () {
160
- const { name: ns } = this
208
+ const { ns } = this
161
209
  if (method === 'start') freeze(me[ns].config)
210
+ /**
211
+ * Run before ```{method}``` is executed within ```{ns}``` context
212
+ *
213
+ * - ```{ns}``` - namespace
214
+ * - ```{method}``` - Accepted methods: ```Init``` or ```Start```
215
+ *
216
+ * @global
217
+ * @event {ns}:before{method}
218
+ * @see module:Helper/Base.run
219
+ */
162
220
  await runHook(`${ns}:${camelCase(`before ${method}`)}`)
163
221
  await me[ns][method]()
222
+ /**
223
+ * Run after ```{method}``` is executed within ```{ns}``` context
224
+ *
225
+ * - ```{ns}``` - namespace
226
+ * - ```{method}``` - Accepted methods: ```Init``` or ```Start```
227
+ *
228
+ * @global
229
+ * @event {ns}:after{method}
230
+ * @see module:Helper/Base.run
231
+ */
164
232
  await runHook(`${ns}:${camelCase(`after ${method}`)}`)
165
233
  })
166
- await runHook(`bajo:${camelCase(`after ${method} all plugins`)}`)
234
+ /**
235
+ * Run after all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
236
+ *
237
+ * @global
238
+ * @event bajo:afterAll{method}
239
+ * @see module:Helper/Base.run
240
+ */
241
+ await runHook(`bajo:${camelCase(`after all ${method}`)}`)
167
242
  }
168
- me.bajo.log.debug('loadedPlugins%s', join(map(me.bajo.pluginPkgs, b => camelCase(b))))
169
243
  }
@@ -4,28 +4,60 @@ const { isPlainObject, each, isArray, get, isEmpty, merge } = lodash
4
4
  Error.stackTraceLimit = 15
5
5
 
6
6
  /**
7
- * Bajo error class, a thin wrapper of node's Error object. Use this name instead of Error
8
- * because, of course, Error is a reserved keyword in node.
7
+ * Bajo error class, a thin wrapper of node's Error object.
8
+ *
9
+ * Every Bajo {@link Plugin|plugin} has a built-in method called ```error``` which basically the shortcut to create a new Err instance.
10
+ * It helps you create this instance anywhere in your code quickly without the hassle of importing & instantiating:
11
+ *
12
+ * ```javascript
13
+ * ... anywhere inside your code
14
+ * if (notfound) throw this.error('Sorry, item is nowhere to be found!')
15
+ * ```
9
16
  */
10
17
  class Err {
11
18
  /**
12
- * @param {Object} plugin - Plugin instance
19
+ * @param {Plugin} plugin - Plugin instance
13
20
  * @param {string} msg - Error message
14
- * @param {...any} [args] - Variables to interpolate with error message. Payload object can be pushed as the very last argument
21
+ * @param {...any} [args] - Variables to interpolate with error message. Payload object can be pushed at the very last argument
15
22
  */
16
23
  constructor (plugin, msg, ...args) {
24
+ /**
25
+ * Attached plugin
26
+ * @type {Plugin}
27
+ */
17
28
  this.plugin = plugin
29
+
30
+ /**
31
+ * The app instance
32
+ * @type {App}
33
+ */
34
+ this.app = plugin.app
35
+
36
+ /**
37
+ * Error payload extracted from the last arguments
38
+ * @type {Object}
39
+ */
18
40
  this.payload = args.length > 0 && isPlainObject(args[args.length - 1]) ? args[args.length - 1] : {}
41
+
42
+ /**
43
+ * Original message before translation
44
+ * @type {string}
45
+ */
19
46
  this.orgMessage = msg
20
- this.message = this.payload.noTrans ? msg : plugin.print.write(msg, ...args)
47
+
48
+ /**
49
+ * Translated message
50
+ * @type {string}
51
+ */
52
+ this.message = this.payload.noTrans ? msg : this.plugin.t(msg, ...args)
21
53
  this.write()
22
54
  }
23
55
 
24
56
  /**
25
- * Create the error object
57
+ * Write message to the console
26
58
  *
27
59
  * @method
28
- * @returns {Object} Error object
60
+ * @returns {Err} Error object, usefull for chaining
29
61
  */
30
62
  write = () => {
31
63
  let err
@@ -46,20 +78,20 @@ class Err {
46
78
  err[key] = value
47
79
  }
48
80
  if (!isEmpty(values)) err.values = values
49
- err.ns = this.plugin.name
81
+ err.ns = this.plugin.ns
50
82
  err.orgMessage = this.orgMessage
51
83
  return err
52
84
  }
53
85
 
54
86
  /**
55
- * Print error object on screen and terminate app process
87
+ * Print instance on console and terminate process
56
88
  *
57
89
  * @method
58
90
  */
59
91
  fatal = () => {
60
92
  const err = this.write()
61
93
  console.error(err)
62
- process.kill(process.pid, 'SIGINT')
94
+ this.app.exit()
63
95
  }
64
96
 
65
97
  /**
@@ -70,21 +102,19 @@ class Err {
70
102
  * @returns {Object}
71
103
  */
72
104
  formatErrorDetails = (value) => {
73
- const { isString } = this.plugin.app.bajo.lib._
105
+ const { isString } = this.app.lib._
74
106
  const result = {}
75
107
  const me = this
76
108
  each(value, (v, i) => {
77
- const print = me.plugin.print
78
109
  if (isString(v)) v = { error: v }
79
110
  if (!v.context) return undefined
80
111
  v.context.message = v.message
81
- // if (v.type === 'any.only') v.context.ref = print.write(`field.${get(v, 'context.valids.0.key')}`)
82
112
  if (v.type === 'any.only') v.context.ref = get(v, 'context.valids', []).join(', ')
83
113
  const field = get(v, 'context.key')
84
114
  const val = get(v, 'context.value')
85
115
  value[i] = {
86
116
  field,
87
- error: print.write(`validation.${v.type}`, v.context ?? {}, {}),
117
+ error: me.plugin.t(`validation.${v.type}`, v.context ?? {}, {}),
88
118
  value: val,
89
119
  ext: { type: v.type, context: v.context }
90
120
  }
@@ -0,0 +1,212 @@
1
+ import os from 'os'
2
+ import lodash from 'lodash'
3
+ import dayjs from 'dayjs'
4
+ import fs from 'fs-extra'
5
+ import logLevels from '../../lib/log-levels.js'
6
+ import chalk from 'chalk'
7
+
8
+ const { isEmpty, without, merge, get } = lodash
9
+
10
+ /**
11
+ * Log output in stringified JSON format. Returned when app run in ```prod``` environment
12
+ *
13
+ * @typedef TLogJson
14
+ * @property {string} prefix - Message prefix
15
+ * @property {string} message - The message itself
16
+ * @property {string} level - Log level
17
+ * @property {number} time - Time in millisecond
18
+ * @property {number} pid - Process ID
19
+ * @property {string} hostname - Hostname
20
+ * @property {Object} [data] - Payload data, if any
21
+ * @see Log#formatMsg
22
+ */
23
+
24
+ /**
25
+ * A thin & fast logger system.
26
+ *
27
+ * An instance is created by the {@link App|app} and available to use anywhere like this:
28
+ *
29
+ * ```javascript
30
+ * ... anywhere inside your code
31
+ * this.app.log.debug(...)
32
+ * ```
33
+ *
34
+ * Shortcuts to log's methods are also available on every Bajo {@link Plugin|plugin}. Call on
35
+ * these shortcuts will be prefixed with it's plugin name automatically:
36
+ *
37
+ * ```javascript
38
+ * ... anywhere inside your code
39
+ * if (!isValid) this.log.error('Invalid value!')
40
+ * ```
41
+ *
42
+ * @class
43
+ */
44
+ class Log {
45
+ /**
46
+ * @param {App} app - App instance
47
+ */
48
+ constructor (app) {
49
+ this.lastDelta = 0
50
+ /**
51
+ * The app instance
52
+ * @type {App}
53
+ */
54
+ this.app = app
55
+
56
+ /**
57
+ * Date format to use in {@link https://day.js.org/docs/en/parse/string-format|dayjs} format. See {@tutorial config} for more info.
58
+ * @type {string}
59
+ */
60
+ const { dateFormat } = this.app.bajo.config.log ?? {}
61
+ this.dateFormat = dateFormat ?? 'YYYY-MM-DDTHH:mm:ss.SSS'
62
+ }
63
+
64
+ /**
65
+ * Display & format message according to one of these rules:
66
+ * 1. ```level``` ```prefix``` ```text``` ```var 1``` ```var 2``` ```...var n``` - Translate ```text``` and interpolate with ```vars``` for level ```level```
67
+ * 2. ```level``` ```prefix``` ```data``` ```text``` ```var 1``` ```var 2``` ```...var n``` - As above, and append stringified ```data```
68
+ * 3. ```level``` ```prefix``` ```error``` - Format as {@link Err} object. If current log level is _trace_, dump it on screen
69
+ *
70
+ * In ```prod``` environment, log will be delivered as JSON stringified object. See {@link TLogJson} for more info
71
+ *
72
+ * @method
73
+ * @param {string} level - Log level to use
74
+ * @param {string} prefix - Prefix to the message
75
+ * @param {...any} params - See format above
76
+ * @see Err
77
+ * @see TLogJson
78
+ */
79
+ formatMsg = (level, prefix, ...params) => {
80
+ if (this.app.bajo.config.log.level === 'silent') return
81
+ if (!this.app.bajo.isLogInRange(level)) return
82
+ const pretty = this.app.bajo.config.log.pretty
83
+ let [data, msg, ...args] = params
84
+ if (typeof data === 'string') {
85
+ args.unshift(msg)
86
+ msg = data
87
+ data = null
88
+ }
89
+ args = without(args, undefined)
90
+ if (data instanceof Error) {
91
+ msg = 'error%s'
92
+ args = [data.message]
93
+ }
94
+ msg = this.app.t(prefix, msg, ...args)
95
+ let text
96
+ const dt = new Date()
97
+ let diff = null
98
+ const timeTaken = !!get(this, 'app.bajo.config.log.timeTaken')
99
+ if (timeTaken) {
100
+ const delta = dayjs(dt).diff(this.app.runAt, 'ms')
101
+ diff = delta - this.lastDelta
102
+ this.lastDelta = delta
103
+ }
104
+ if (this.app.bajo.config.env === 'prod') {
105
+ const json = { prefix, msg, level: logLevels[level].number, time: dt.valueOf(), pid: process.pid, hostname: os.hostname() }
106
+ if (!isEmpty(data)) merge(json, { data })
107
+ if (timeTaken) merge(json, { timeTaken: diff })
108
+ text = JSON.stringify(json)
109
+ } else {
110
+ let dateFormat = get(this, 'app.bajo.config.log.dateFormat', this.dateFormat).replaceAll('[Z]', '')
111
+ const localDate = get(this, 'app.bajo.config.log.localDate', false)
112
+ let date = dayjs(dt)
113
+ if (!localDate) date = date.utc()
114
+ if (!(dateFormat.includes('L') || dateFormat.includes('l'))) dateFormat += '[Z]'
115
+ date = date.format(dateFormat)
116
+ let tdate = pretty ? chalk.cyan(date) : `[${date}]`
117
+ if (timeTaken) {
118
+ const tdiff = pretty ? chalk.cyan(`+${diff}ms`) : `[+${diff}ms]`
119
+ tdate += ` ${tdiff}`
120
+ }
121
+ const tlevel = pretty ? `${chalk[logLevels[level].color](level.toUpperCase())}:` : `[${level.toUpperCase()}]`
122
+ const tprefix = pretty ? chalk.bgBlue(`${prefix}`) : `[${prefix}]`
123
+ text = `${tdate} ${tlevel} ${tprefix} ${msg}`
124
+ if (!isEmpty(data)) text += '\n' + JSON.stringify(data)
125
+ }
126
+ console.log(text)
127
+ if (this.app.bajo.config.log.save) {
128
+ fs.ensureDirSync(`${this.app.bajo.dir.data}/log`)
129
+ // TODO: log write, rotation, etc
130
+ }
131
+ if (data instanceof Error && level === 'trace') console.error(data)
132
+ }
133
+
134
+ /**
135
+ * Display & format message in ```trace``` level. See {@link Log#formatMsg|formatMsg} for details
136
+ *
137
+ * @method
138
+ * @param {string} prefix - Message prefix
139
+ * @param {...any} params - Parameters
140
+ */
141
+ trace = (prefix, ...params) => {
142
+ this.formatMsg('trace', prefix, ...params)
143
+ }
144
+
145
+ /**
146
+ * Display & format message in ```debug``` level. See {@link Log#formatMsg|formatMsg} for details
147
+ *
148
+ * @method
149
+ * @param {string} prefix - Message prefix
150
+ * @param {...any} params - Parameters
151
+ */
152
+ debug = (prefix, ...params) => {
153
+ this.formatMsg('debug', prefix, ...params)
154
+ }
155
+
156
+ /**
157
+ * Display & format message in ```info``` level. See {@link Log#formatMsg|formatMsg} for details
158
+ *
159
+ * @method
160
+ * @param {string} prefix - Message prefix
161
+ * @param {...any} params - Parameters
162
+ */
163
+ info = (prefix, ...params) => {
164
+ this.formatMsg('info', prefix, ...params)
165
+ }
166
+
167
+ /**
168
+ * Display & format message in ```warn``` level. See {@link Log#formatMsg|formatMsg} for details
169
+ *
170
+ * @method
171
+ * @param {string} prefix - Message prefix
172
+ * @param {...any} params - Parameters
173
+ */
174
+ warn = (prefix, ...params) => {
175
+ this.formatMsg('warn', prefix, ...params)
176
+ }
177
+
178
+ /**
179
+ * Display & format message in ```error``` level. See {@link Log#formatMsg|formatMsg} for details
180
+ *
181
+ * @method
182
+ * @param {string} prefix - Message prefix
183
+ * @param {...any} params - Parameters
184
+ */
185
+ error = (prefix, ...params) => {
186
+ this.formatMsg('error', prefix, ...params)
187
+ }
188
+
189
+ /**
190
+ * Display & format message in ```fatal``` level. See {@link Log#formatMsg|formatMsg} for details
191
+ *
192
+ * @method
193
+ * @param {string} prefix - Message prefix
194
+ * @param {...any} params - Parameters
195
+ */
196
+ fatal = (prefix, ...params) => {
197
+ this.formatMsg('fatal', prefix, ...params)
198
+ }
199
+
200
+ /**
201
+ * Display & format message in ```silent``` level. See {@link Log#formatMsg|formatMsg} for details
202
+ *
203
+ * @method
204
+ * @param {string} prefix - Message prefix
205
+ * @param {...any} params - Parameters
206
+ */
207
+ silent = (prefix, ...params) => {
208
+ this.formatMsg('silent', prefix, ...params)
209
+ }
210
+ }
211
+
212
+ export default Log