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
File without changes
File without changes
package/.jsdoc.conf.json CHANGED
File without changes
package/.mocharc.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "spec": "test/**/*.test.js",
3
+ "timeout": 5000
4
+ }
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
File without changes
@@ -1,11 +1,7 @@
1
1
  import os from 'os'
2
- import lodash from 'lodash'
3
- import dayjs from 'dayjs'
4
- import fs from 'fs-extra'
5
2
  import logLevels from '../../lib/log-levels.js'
6
3
  import chalk from 'chalk'
7
-
8
- const { isEmpty, without, merge, get } = lodash
4
+ import { stripVTControlCharacters } from 'node:util'
9
5
 
10
6
  /**
11
7
  * Log output in stringified JSON format. Returned when app run in ```prod``` environment
@@ -53,12 +49,9 @@ class Log {
53
49
  */
54
50
  this.app = app
55
51
 
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'
52
+ const { fs } = this.app.lib
53
+ this.logDir = `${this.app.bajo.dir.data}/log`
54
+ if (this.app.bajo.config.log.save) fs.ensureDirSync(this.logDir)
62
55
  }
63
56
 
64
57
  /**
@@ -77,9 +70,12 @@ class Log {
77
70
  * @see TLogJson
78
71
  */
79
72
  formatMsg = (level, prefix, ...params) => {
73
+ const { dayjs } = this.app.lib
74
+ const { isEmpty, merge, without } = this.app.lib._
75
+
80
76
  if (this.app.bajo.config.log.level === 'silent') return
81
77
  if (!this.app.bajo.isLogInRange(level)) return
82
- const pretty = this.app.bajo.config.log.pretty
78
+ const { useUtc, timeTaken, dateFormat, pretty } = this.app.bajo.config.log
83
79
  let [data, msg, ...args] = params
84
80
  if (typeof data === 'string') {
85
81
  args.unshift(msg)
@@ -93,11 +89,10 @@ class Log {
93
89
  }
94
90
  msg = this.app.t(prefix, msg, ...args)
95
91
  let text
96
- const dt = new Date()
92
+ const dt = dayjs()
97
93
  let diff = null
98
- const timeTaken = !!get(this, 'app.bajo.config.log.timeTaken')
99
94
  if (timeTaken) {
100
- const delta = dayjs(dt).diff(this.app.runAt, 'ms')
95
+ const delta = dt.diff(this.app.runAt, 'ms')
101
96
  diff = delta - this.lastDelta
102
97
  this.lastDelta = delta
103
98
  }
@@ -107,11 +102,8 @@ class Log {
107
102
  if (timeTaken) merge(json, { timeTaken: diff })
108
103
  text = JSON.stringify(json)
109
104
  } 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]'
105
+ let date = dt.clone()
106
+ if (useUtc) date = dayjs.utc(dt)
115
107
  date = date.format(dateFormat)
116
108
  let tdate = pretty ? chalk.cyan(date) : `[${date}]`
117
109
  if (timeTaken) {
@@ -124,11 +116,61 @@ class Log {
124
116
  if (!isEmpty(data)) text += '\n' + JSON.stringify(data)
125
117
  }
126
118
  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
119
  if (data instanceof Error && level === 'trace') console.error(data)
120
+ if (this.app.bajo.config.log.save) this.save(text, prefix)
121
+ }
122
+
123
+ /**
124
+ * Calculate pattern used for log rotation
125
+ *
126
+ * @method
127
+ * @param {boolean} isPrev - If true, calculate previous rotation pattern
128
+ * @returns {string} Calculated pattern
129
+ */
130
+ getRotationPattern = (isPrev) => {
131
+ const { dayjs } = this.app.lib
132
+ const { cycle } = this.app.bajo.config.log.rotation
133
+ if (cycle === 'none') return
134
+ let pattern
135
+ const now = dayjs()
136
+ switch (cycle) {
137
+ case 'monthly': {
138
+ const dt = isPrev ? now.subtract(1, 'month') : now
139
+ pattern = dt.format('YYYY-MM')
140
+ break
141
+ }
142
+ case 'weekly': {
143
+ const dt = isPrev ? now.subtract(1, 'week') : now
144
+ pattern = dt.format(`YYYY-W${dt.week()}`)
145
+ break
146
+ }
147
+ case 'daily': {
148
+ const dt = isPrev ? now.subtract(1, 'day') : now
149
+ pattern = dt.format('YYYY-MM-DD')
150
+ break
151
+ }
152
+ }
153
+ return pattern
154
+ }
155
+
156
+ /**
157
+ * Save log to file in {dataDir}/log
158
+ *
159
+ * @method
160
+ * @param {string} text - Log message to save
161
+ * @param {string} prefix - Use prefix as basename. Defaults to 'bajo'
162
+ */
163
+ save = (text, prefix = 'bajo') => {
164
+ const { fs } = this.app.lib
165
+ const fname = this.app.bajo.config.log.rotation.byPlugin ? prefix : 'bajo'
166
+ let file = `${this.logDir}/${fname}.log`
167
+ const content = stripVTControlCharacters(text)
168
+ const pattern = this.getRotationPattern()
169
+ if (pattern) {
170
+ file = `${this.logDir}/${fname}.${pattern}.log`
171
+ }
172
+ fs.appendFileSync(file, `${content}\n`, 'utf8')
173
+ // TODO: symlink bajo.log to target
132
174
  }
133
175
 
134
176
  /**
@@ -207,6 +249,13 @@ class Log {
207
249
  silent = (prefix, ...params) => {
208
250
  this.formatMsg('silent', prefix, ...params)
209
251
  }
252
+
253
+ /**
254
+ * Dispose internal references
255
+ */
256
+ dispose = () => {
257
+ this.app = null
258
+ }
210
259
  }
211
260
 
212
261
  export default Log
package/class/app.js CHANGED
@@ -7,20 +7,23 @@ import outmatch from 'outmatch'
7
7
  import fs from 'fs-extra'
8
8
  import aneka from 'aneka/index.js'
9
9
  import Base from './base.js'
10
- import resolvePath from '../lib/resolve-path.js'
11
- import parseArgsArgv from '../lib/parse-args-argv.js'
12
- import parseEnv from '../lib/parse-env.js'
10
+ import freeze from '../lib/freeze.js'
13
11
  import { runAsApplet } from './helper/bajo.js'
12
+ import Tools from './plugin/tools.js'
14
13
  import dayjs from 'dayjs'
15
14
  import utc from 'dayjs/plugin/utc.js'
16
15
  import customParseFormat from 'dayjs/plugin/customParseFormat.js'
17
16
  import localizedFormat from 'dayjs/plugin/localizedFormat.js'
17
+ import weekOfYear from 'dayjs/plugin/weekOfYear.js'
18
+ import findDeep from '../lib/find-deep.js'
18
19
 
19
20
  dayjs.extend(utc)
20
21
  dayjs.extend(customParseFormat)
21
22
  dayjs.extend(localizedFormat)
23
+ dayjs.extend(weekOfYear)
22
24
 
23
- const { isPlainObject, get, reverse, map, isString, last, without, keys } = lodash
25
+ const { camelCase, isPlainObject, get, reverse, map, last, without } = lodash
26
+ const { pascalCase } = aneka
24
27
  let unknownLangWarning = false
25
28
 
26
29
  function outmatchNs (source, pattern) {
@@ -52,6 +55,8 @@ function outmatchNs (source, pattern) {
52
55
  * @property {Object} aneka - Access to {@link https://github.com/ardhi/aneka|aneka}
53
56
  * @property {Object} outmatch - Access to {@link https://github.com/axtgr/outmatch|outmatch}
54
57
  * @property {Object} dayjs - Access to {@link https://day.js.org|dayjs} with utc & customParseFormat plugin already applied
58
+ * @property {Object} freeze
59
+ * @property {Object} findDeep
55
60
  * @see App
56
61
  */
57
62
  const lib = {
@@ -61,7 +66,9 @@ const lib = {
61
66
  sprintf,
62
67
  outmatch,
63
68
  dayjs,
64
- aneka
69
+ aneka,
70
+ freeze,
71
+ findDeep
65
72
  }
66
73
 
67
74
  /**
@@ -70,26 +77,26 @@ const lib = {
70
77
  * @class
71
78
  */
72
79
  class App {
73
- /**
74
- * Your main namespace. And yes, you suppose to NOT CHANGE this
75
- *
76
- * @memberof App
77
- * @constant {string}
78
- * @default 'main'
79
- */
80
- static mainNs = 'main'
81
-
82
- /**
83
- * App environments
84
- * @memberof App
85
- * @constant {TAppEnv}
86
- */
87
- static envs = { dev: 'development', prod: 'production' }
88
-
89
80
  /**
90
81
  * @param {string} cwd - Current working dirctory
91
82
  */
92
83
  constructor (cwd) {
84
+ /**
85
+ * Your main namespace. And yes, you suppose to NOT CHANGE this
86
+ *
87
+ * @memberof App
88
+ * @constant {string}
89
+ * @default 'main'
90
+ */
91
+ this.mainNs = 'main'
92
+
93
+ /**
94
+ * App environments
95
+ * @memberof App
96
+ * @constant {TAppEnv}
97
+ */
98
+ this.envs = { dev: 'development', prod: 'production' }
99
+
93
100
  /**
94
101
  * Date/time when your app start
95
102
  * @type {Date}
@@ -143,6 +150,20 @@ class App {
143
150
  */
144
151
  this.lib = lib
145
152
  this.lib.outmatchNs = outmatchNs.bind(this)
153
+ this.lib.parseObject = (obj, options = {}) => {
154
+ const me = this
155
+ const { ns, lang } = options
156
+ options.translator = {
157
+ lang,
158
+ prefix: 't:',
159
+ handler: val => {
160
+ const scope = ns ? me.app[ns] : me
161
+ const [text, ...args] = val.split('|')
162
+ return scope.t(text, ...args, { lang })
163
+ }
164
+ }
165
+ return aneka.parseObject(obj, options)
166
+ }
146
167
 
147
168
  /**
148
169
  * Instance of system log
@@ -152,16 +173,16 @@ class App {
152
173
  this.log = {}
153
174
 
154
175
  /**
155
- * All plugin's class definitions are saved here as key-value pairs with plugin name as its key.
156
- * The special key ```base``` is for {@link Base}'s class so anytime you want to
176
+ * All plugin's base class are saved here as key-value pairs with plugin name as its key.
177
+ * The special key ```Base``` && ```Tools``` is for {@link Base} & {@link Tools} class so anytime you want to
157
178
  * create your own plugin, just use something like this:
158
179
  *
159
180
  * ```javascript
160
- * class MyPlugin extends this.app.pluginClass.base {
181
+ * class MyPlugin extends this.app.baseClass.Base {
161
182
  * ... your class
162
183
  * }
163
184
  */
164
- this.pluginClass = { base: Base }
185
+ this.baseClass = { Base, Tools }
165
186
 
166
187
  /**
167
188
  * If app runs in applet mode, this will be the applet's name
@@ -238,35 +259,31 @@ class App {
238
259
  const parts = l.split('=')
239
260
  cwd = parts[1]
240
261
  }
241
- this.dir = resolvePath(cwd)
262
+ this.dir = aneka.resolvePath(cwd)
242
263
  process.env.APPDIR = this.dir
243
264
  }
244
265
 
245
- get mainNs () {
246
- return this.constructor.mainNs
247
- }
248
-
249
266
  /**
250
- * Add and save plugin and it's class definition (if provided)
267
+ * Add and save plugin and it's base class definition (if provided)
251
268
  *
252
269
  * @method
253
270
  * @param {TPlugin} plugin - A valid bajo plugin
254
- * @param {Object} [pluginClass] - Plugin's class definition
271
+ * @param {Object} [baseClass] - Base class definition
255
272
  */
256
- addPlugin = (plugin, pluginClass) => {
273
+ addPlugin = (plugin, baseClass) => {
257
274
  if (this[plugin.ns]) throw new Error(`Plugin '${plugin.ns}' added already`)
258
275
  this[plugin.ns] = plugin
259
- if (pluginClass) this.pluginClass[plugin.ns] = pluginClass
276
+ if (baseClass) this.baseClass[pascalCase(plugin.ns)] = baseClass
260
277
  }
261
278
 
262
279
  /**
263
- * Get all loaded plugin namesspaces
280
+ * Get all loaded plugin namespaces
264
281
  *
265
282
  * @method
266
283
  * @returns {string[]}
267
284
  */
268
285
  getAllNs = () => {
269
- return without(keys(this.pluginClass), 'base')
286
+ return this.pluginPkgs.map(pkg => camelCase(pkg))
270
287
  }
271
288
 
272
289
  /**
@@ -282,7 +299,8 @@ class App {
282
299
  const result = util.inspect(arg, { depth: 10, colors: true })
283
300
  console.log(result)
284
301
  }
285
- if (terminate) process.kill(process.pid, 'SIGINT')
302
+ // if (terminate) process.kill(process.pid, 'SIGINT')
303
+ if (terminate) process.exit('1')
286
304
  }
287
305
 
288
306
  /**
@@ -292,27 +310,27 @@ class App {
292
310
  * - Create {@link Bajo|Bajo} instance & initialize it
293
311
  * - {@link module:Helper/Bajo.runAsApplet|Run in applet mode} if ```-a``` or ```--applet``` is given
294
312
  *
295
- * After boot process is completed, event ```bajo:afterBootComplete``` is emitted.
313
+ * After boot process is completed, event ```bajo:afterBootCompleted``` is emitted.
296
314
  *
297
315
  * If app mode is ```applet```, it runs your choosen applet instead.
298
316
  *
299
317
  * @method
300
318
  * @async
301
319
  * @returns {App}
302
- * @fires bajo:afterBootComplete
320
+ * @fires bajo:afterBootCompleted
303
321
  */
304
322
  boot = async () => {
305
323
  // argv/args/env
306
324
  this.bajo = new Bajo(this)
307
- const { argv, args } = await parseArgsArgv.call(this) ?? {}
325
+ const { argv, args } = await aneka.parseArgsArgv() ?? {}
308
326
  this.args = args
309
- this.argv = argv
310
- this.envVars = parseEnv.call(this)
327
+ this.argv = aneka.parseObject(argv, { parseValue: true })
328
+ this.envVars = aneka.parseObject(aneka.parseEnv(), { parseValue: true })
311
329
  this.applet = this.envVars._.applet ?? this.argv._.applet
312
330
  await this.bajo.init()
313
331
  // boot complete
314
332
  const elapsed = new Date() - this.runAt
315
- this.bajo.log.debug('bootCompleted%s', this.lib.aneka.secToHms(elapsed, true))
333
+ this.bajo.log.debug('bootCompleted%s', aneka.secToHms(elapsed, true))
316
334
  /**
317
335
  * Run after boot process is completed
318
336
  *
@@ -321,7 +339,7 @@ class App {
321
339
  * @see {@tutorial hook}
322
340
  * @see App#boot
323
341
  */
324
- await this.bajo.runHook('bajo:afterBootComplete')
342
+ await this.bajo.runHook('bajo:afterBootCompleted')
325
343
  if (this.applet) await runAsApplet.call(this.bajo)
326
344
  return this
327
345
  }
@@ -333,7 +351,8 @@ class App {
333
351
  * @param {string} [signal=SIGINT] - Signal to send
334
352
  */
335
353
  exit = (signal = 'SIGINT') => {
336
- process.kill(process.pid, 'SIGINT')
354
+ if (signal === true) process.exit('1')
355
+ process.kill(process.pid, signal)
337
356
  }
338
357
 
339
358
  /**
@@ -407,11 +426,7 @@ class App {
407
426
  }
408
427
  }
409
428
  if (!trans) trans = text
410
- const values = map(params, a => {
411
- if (!isString(a)) return a
412
- return a
413
- })
414
- return sprintf(trans, ...values)
429
+ return aneka.formatText(trans, ...params)
415
430
  }
416
431
 
417
432
  /**