bajo 2.0.0 → 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 +136 -85
  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 -177
  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
package/class/bajo.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import Plugin from './plugin.js'
2
- import BasePlugin from './base/plugin.js'
3
2
  import increment from 'add-filename-increment'
4
3
  import fs from 'fs-extra'
5
4
  import path from 'path'
@@ -19,6 +18,15 @@ import resolvePath from '../lib/resolve-path.js'
19
18
  import importModule from '../lib/import-module.js'
20
19
  import logLevels from '../lib/log-levels.js'
21
20
  import { types as formatTypes, formats } from '../lib/formats.js'
21
+ import {
22
+ buildBaseConfig,
23
+ buildExtConfig,
24
+ buildPlugins,
25
+ collectConfigHandlers,
26
+ bootOrder,
27
+ bootPlugins,
28
+ exitHandler
29
+ } from './helper/bajo.js'
22
30
 
23
31
  const require = createRequire(import.meta.url)
24
32
 
@@ -31,67 +39,30 @@ const {
31
39
 
32
40
  /**
33
41
  * The Core. The main engine. The one and only plugin that control app's boot process and
34
- * making sure all other plugins working smoothly.
42
+ * making sure all other plugins working nicely.
35
43
  *
36
44
  * @class
37
45
  */
38
- class Bajo extends BasePlugin {
46
+ class Bajo extends Plugin {
47
+ static alias = 'bajo'
39
48
  /**
40
- * @param {Object} app
41
- * @param {Object} app - App instance reference. Usefull to call app method inside a plugin
49
+ * @param {App} app - App instance. Usefull to call app method inside a plugin
42
50
  */
43
51
  constructor (app) {
44
52
  super('bajo', app)
53
+ this.whiteSpace = [' ', '\t', '\n', '\r']
45
54
  /**
46
- * Date/time when your app start
47
- * @type {Date}
48
- */
49
- this.runAt = new Date()
50
-
51
- /**
52
- * Your main namespace. And yes, you suppose NOT to change this
53
- *
54
- * @type {string}
55
- */
56
- this.mainNs = 'main'
57
-
58
- /**
59
- * Storage for applets
60
- *
61
- * @type {Array}
62
- */
63
- this.applets = []
64
-
65
- /**
66
- * List of all loaded plugin's package names
67
- *
68
- * @type {Array}
69
- */
70
- this.pluginPkgs = []
71
-
72
- /**
73
- * List of all loaded plugin's names
55
+ * Config object
74
56
  *
75
- * @type {Array}
57
+ * @type {Object}
58
+ * @see {@tutorial config}
76
59
  */
77
- this.pluginNames = []
60
+ this.config = {}
78
61
 
79
- /**
80
- * Storage for config handlers. By default there are only two handlers available: ```.js```
81
- * and ```.json```.
82
- *
83
- * Use plugin to add more type - e.g: {@link https://github.com/ardhi/bajo-config|bajo-config}
84
- * lets you to use ```.yaml``` and ```.toml```
85
- *
86
- * @type {Array}
87
- */
88
- this.configHandlers = [
62
+ app.configHandlers = [
89
63
  { ext: '.js', readHandler: this._defConfigHandler },
90
- { ext: '.json', readHandler: this.readJson }
64
+ { ext: '.json', readHandler: this.fromJson, writeHandler: this.toJson }
91
65
  ]
92
- this.whiteSpace = [' ', '\t', '\n', '\r']
93
- this.envs = { dev: 'development', staging: 'staging', prod: 'production' }
94
- this.lib.Plugin = Plugin
95
66
  }
96
67
 
97
68
  async _defConfigHandler (file, opts = {}) {
@@ -100,6 +71,30 @@ class Bajo extends BasePlugin {
100
71
  return mod
101
72
  }
102
73
 
74
+ /**
75
+ * Initialization:
76
+ *
77
+ * 1. Building {@link module:Helper/Bajo.buildBaseConfig|base config}
78
+ * 2. {@link module:Helper/Bajo.buildPlugins|Building plugins}
79
+ * 3. Collect all {@link module:Helper/Bajo.collectConfigHandlers|config handler}
80
+ * 4. Building {@link module:Helper/Bajo.buildExtConfig|extra config}
81
+ * 5. Setup {@link module:Helper/Bajo.bootOrder|boot order}
82
+ * 6. {@link module:Helper/Bajo.bootPlugins|Boot loaded plugins}
83
+ * 7. Attach {@link module:Helper/Bajo.exitHandler|exit handlers}
84
+ *
85
+ * @method
86
+ * @async
87
+ */
88
+ init = async () => {
89
+ await buildBaseConfig.call(this)
90
+ await collectConfigHandlers.call(this)
91
+ await buildExtConfig.call(this)
92
+ await buildPlugins.call(this)
93
+ await bootOrder.call(this)
94
+ await bootPlugins.call(this)
95
+ await exitHandler.call(this)
96
+ }
97
+
103
98
  /**
104
99
  * Resolve file name to filesystem's path. Windows path separator ```\```
105
100
  * is normalized to Unix's ```/```
@@ -117,10 +112,10 @@ class Bajo extends BasePlugin {
117
112
  * Freeze object
118
113
  *
119
114
  * @method
120
- * @param {Object} obj - Object
121
- * @param {boolean} [shallow=false] - If true (default), deep freeze object
115
+ * @param {Object} obj - Object to freeze
116
+ * @param {boolean} [shallow=false] - If ```false``` (default), deep freeze object
122
117
  */
123
- freeze = (obj, shallow) => {
118
+ freeze = (obj, shallow = false) => {
124
119
  if (shallow) Object.freeze(obj)
125
120
  else deepFreeze(obj)
126
121
  }
@@ -151,6 +146,25 @@ class Bajo extends BasePlugin {
151
146
  return { ns, subNs, path: _path, fullNs: names.join('.'), type }
152
147
  }
153
148
 
149
+ /**
150
+ * Name based ```{ns}:{path}``` format
151
+ * @typedef {string} TNsPathPairs
152
+ * @see TNsPathResult
153
+ * @see Bajo#buildNsPath
154
+ * @see Bajo#breakNsPath
155
+ */
156
+
157
+ /**
158
+ * Build ns/path pairs
159
+ *
160
+ * @method
161
+ * @param {object} [options={}] - Options object
162
+ * @param {string} [options.ns=''] - Namespace
163
+ * @param {string} [options.subNs] - Sub namespace
164
+ * @param {string} [options.subSubNs] - Sub sub namespace
165
+ * @param {string} [options.path] - Path
166
+ * @returns {TNsPathPairs} - Ns/path pairs
167
+ */
154
168
  buildNsPath = ({ ns = '', subNs, subSubNs, path } = {}) => {
155
169
  if (subNs) ns += '.' + subNs
156
170
  if (subSubNs) ns += '.' + subSubNs
@@ -158,23 +172,26 @@ class Bajo extends BasePlugin {
158
172
  }
159
173
 
160
174
  /**
161
- * Object returned by {@link BajoCore#breakNsPath|breakNsPath}
175
+ * Object returned by {@link Bajo#breakNsPath|bajo:breakNsPath}
162
176
  *
163
- * @typedef {Object} NsPathType
177
+ * @typedef {Object} TNsPathResult
164
178
  * @property {string} ns - Namespace
165
179
  * @property {string} [subNs] - Sub namespace
166
180
  * @property {string} [subSubNs] - Sub of sub namespace
167
181
  * @property {string} path - Path without query string or hash
168
182
  * @property {string} fullPath - Full path, including query string and hash
183
+ * @see TNsPathPairs
184
+ * @see Bajo#buildNsPath
185
+ * @see Bajo#breakNsPath
169
186
  */
170
187
 
171
188
  /**
172
189
  * Break name to its namespace & path infos
173
190
  *
174
191
  * @method
175
- * @param {string} name - Name to break
176
- * @param {boolean} [checkNs=true] - If true (default), namespace will be checked for its validity
177
- * @returns {NsPathType}
192
+ * @param {(TNsPathPairs|string)} name - Name to break
193
+ * @param {boolean} [checkNs=true] - If ```true``` (default), namespace will be checked for its validity
194
+ * @returns {TNsPathResult}
178
195
  */
179
196
  breakNsPath = (name = '', checkNs = true) => {
180
197
  let [ns, ...path] = name.split(':')
@@ -190,7 +207,7 @@ class Bajo extends BasePlugin {
190
207
  if (checkNs) {
191
208
  if (!this.app[ns]) {
192
209
  const plugin = this.getPlugin(ns)
193
- if (plugin) ns = plugin.name
210
+ if (plugin) ns = plugin.ns
194
211
  }
195
212
  if (!this.app[ns]) throw this.error('unknownPluginOrNotLoaded%s')
196
213
  }
@@ -222,31 +239,39 @@ class Bajo extends BasePlugin {
222
239
  }
223
240
 
224
241
  /**
225
- * Method to transform an array or object from config into an array of collection safely.
226
- *
227
- * Emitted hooks:
228
- * 1. ```{ns}:beforeBuildCollection (container)``` - called before collection is built
229
- * 2. ```{ns}:afterBuildCollection (container, items)``` - called after collection is built
242
+ * Method to transform config's array or object into an array of collection.
230
243
  *
231
244
  * @method
232
245
  * @async
233
246
  * @param {Object} options - Options
234
247
  * @param {string} [options.ns] - Namespace. If not provided, defaults to ```bajo```
235
248
  * @param {function} [options.handler] - Handler to call while building the collection item
236
- * @param {Array} [options.dupChecks=[]] - Array of keys to check for duplicates
249
+ * @param {string[]} [options.dupChecks=[]] - Array of keys to check for duplicates
237
250
  * @param {string} options.container - Key used as container name
238
251
  * @param {boolean} [options.useDefaultName=true] - If true (default) and ```name``` key is not provided, returned collection will be named ```default```
239
- * @returns {Array} The collection
252
+ * @fires bajo:beforeBuildCollection
253
+ * @fires bajo:afterBuildCollection
254
+ * @returns {Object[]} The collection
240
255
  */
241
256
  buildCollections = async (options = {}) => {
242
257
  let { ns, handler, dupChecks = [], container, useDefaultName } = options
243
258
  useDefaultName = useDefaultName ?? true
244
- if (!ns) ns = this.name
259
+ if (!ns) ns = this.ns
245
260
  const cfg = this.app[ns].getConfig()
246
261
  let items = get(cfg, container, [])
247
262
  if (!isArray(items)) items = [items]
248
- this.app[ns].log.trace('collecting%s', this.app[ns].print.write(container))
249
- await this.runHook(`${ns}:${camelCase('beforeBuildCollection')}`, container)
263
+ this.app[ns].log.trace('collecting%s', this.t(container))
264
+
265
+ /**
266
+ * Run before collection is built
267
+ *
268
+ * @global
269
+ * @event bajo:beforeBuildCollection
270
+ * @param {string} container
271
+ * @see {@tutorial hook}
272
+ * @see Bajo#buildCollections
273
+ */
274
+ await this.runHook(`${ns}:beforeBuildCollection`, container)
250
275
  const deleted = []
251
276
  for (const index in items) {
252
277
  const item = items[index]
@@ -260,7 +285,7 @@ class Bajo extends BasePlugin {
260
285
  const result = await handler.call(this.app[ns], { item, index, cfg })
261
286
  if (result) items[index] = result
262
287
  else if (result === false) deleted.push(index)
263
- if (this.app.bajo.applet && item.skipOnTool && !deleted.includes(index)) deleted.push(index)
288
+ if (this.app.applet && item.skipOnApplet && !deleted.includes(index)) deleted.push(index)
264
289
  }
265
290
  if (deleted.length > 0) pullAt(items, deleted)
266
291
 
@@ -272,13 +297,25 @@ class Bajo extends BasePlugin {
272
297
  if (checkers.includes(checker)) this.app[ns].fatal('oneOrMoreSharedTheSame%s%s', container, this.join(dupChecks.filter(i => !isFunction(i))))
273
298
  }
274
299
  }
275
- await this.runHook(`${ns}:${camelCase('afterBuildCollection')}`, container, items)
276
- this.app[ns].log.debug('collected%s%d', this.app[ns].print.write(container), items.length)
300
+
301
+ /**
302
+ * Run after collection is built
303
+ *
304
+ * @global
305
+ * @event bajo:afterBuildCollection
306
+ * @param {string} container
307
+ * @param {Object[]} items
308
+ * @see {@tutorial hook}
309
+ * @see Bajo#buildCollections
310
+ */
311
+ await this.runHook(`${ns}:afterBuildCollection`, container, items)
312
+ this.app[ns].log.debug('collected%s%d', this.t(container), items.length)
277
313
  return items
278
314
  }
279
315
 
280
316
  /**
281
- * Calling any plugin's method by its name. Name format: ```ns:methodName```.
317
+ * Calling any plugin's method by its name:
318
+ *
282
319
  * - If name is a string, the corresponding plugin's method will be called with passed args as its parameters
283
320
  * - If name is a plugin instance, this will be used as the scope instead. The first args is now the handler name and the rest are its parameters
284
321
  * - If name is a function, this function will be run under scope with the remaining args
@@ -286,7 +323,7 @@ class Bajo extends BasePlugin {
286
323
  *
287
324
  * @method
288
325
  * @async
289
- * @param {(string|Object|function)} name - Method's name, function handler, plain object or plugin instance
326
+ * @param {(TNsPathPairs|Object|function)} name - Method's name, function handler, plain object or plugin instance
290
327
  * @param {...any} [args] - One or more arguments passed as parameter to the handler
291
328
  * @returns {any} Returned value
292
329
  */
@@ -299,10 +336,10 @@ class Bajo extends BasePlugin {
299
336
  }
300
337
  const bajo = scope.app.bajo
301
338
  if (isString(item)) {
302
- if (item.startsWith('applet:') && bajo.applets.length > 0) {
339
+ if (item.startsWith('applet:') && bajo.app.applets.length > 0) {
303
340
  const [, ns, path] = item.split(':')
304
- const applet = find(bajo.applets, a => (a.ns === ns || a.alias === ns))
305
- if (applet) result = await bajo.runApplet(applet, path, ...args)
341
+ const applet = find(bajo.app.applets, a => (a.ns === ns || a.alias === ns))
342
+ if (applet && scope.app.bajoCli) result = await scope.app.bajoCli.runApplet(applet, path, ...args)
306
343
  } else {
307
344
  const [ns, method, ...params] = item.split(':')
308
345
  const fn = bajo.getMethod(`${ns}:${method}`)
@@ -330,7 +367,7 @@ class Bajo extends BasePlugin {
330
367
  * @async
331
368
  * @param {function} handler - Function handler. Can be an async function. Scoped to the running plugin
332
369
  * @param {(string|Object)} [options={}] - Options. If a string is provided, it serves as the glob pattern, otherwise:
333
- * @param {(string|Array)} [options.glob] - Glob pattern. If provided,
370
+ * @param {(string|string[])} [options.glob] - Glob pattern. If provided,
334
371
  * @param {boolean} [options.useBajo=false] - If true, add ```bajo``` to the running plugins too
335
372
  * @param {string} [options.prefix=''] - Prepend glob pattern with prefix
336
373
  * @param {boolean} [options.noUnderscore=true] - If true (default), matched file with name starts with underscore is ignored
@@ -340,7 +377,7 @@ class Bajo extends BasePlugin {
340
377
  eachPlugins = async (handler, options = {}) => {
341
378
  if (typeof options === 'string') options = { glob: options }
342
379
  const result = {}
343
- const pluginPkgs = cloneDeep(this.app.bajo.pluginPkgs) ?? []
380
+ const pluginPkgs = cloneDeep(this.app.pluginPkgs) ?? []
344
381
  const { glob, useBajo, prefix = '', noUnderscore = true, returnItems } = options
345
382
  if (useBajo) pluginPkgs.unshift('bajo')
346
383
  for (const pkgName of pluginPkgs) {
@@ -386,11 +423,12 @@ class Bajo extends BasePlugin {
386
423
  }
387
424
 
388
425
  /**
389
- * Object returned by {@link BajoCore#getUnitFormat|getUnitFormat}
426
+ * Object returned by {@link Bajo#getUnitFormat|bajo:getUnitFormat}
390
427
  *
391
- * @typedef {Object} ObjectFormatType
428
+ * @typedef {Object} TBajoFormatResult
392
429
  * @property {string} unitSys - Unit system
393
430
  * @property {Object} format - Format object
431
+ * @see Bajo#getUnitFormat
394
432
  */
395
433
 
396
434
  /**
@@ -400,7 +438,7 @@ class Bajo extends BasePlugin {
400
438
  * @param {Object} [options={}] - Options
401
439
  * @param {string} [options.lang] - Language to use. Defaults to the one you set in config
402
440
  * @param {string} [options.unitSys] - Unit system to use. Defaults to language's unit system or ```metric``` if unspecified
403
- * @returns {ObjectFormatType} - Returned value
441
+ * @returns {TBajoFormatResult} - Returned value
404
442
  */
405
443
  getUnitFormat = (options = {}) => {
406
444
  const lang = options.lang ?? this.config.lang
@@ -413,16 +451,16 @@ class Bajo extends BasePlugin {
413
451
  * Format value by type
414
452
  *
415
453
  * @method
416
- * @param {string} type - Format type. See {@link FormatType} for acceptable values
454
+ * @param {string} type - Format type. See {@link TBajoFormatType} for acceptable values
417
455
  * @param {any} value - Value to format
418
- * @param {string} [dataType] - Value's data type. See {@link DataType} for acceptable values
456
+ * @param {string} [dataType] - Value's data type. See {@link TBajoDataType} for acceptable values
419
457
  * @param {Object} [options={}] - Options
420
458
  * @param {boolean} [options.withUnit=true] - Return with its unit appended
421
459
  * @param {string} [options.lang] - Format value according to this language. Defaults to the one you set in config
422
460
  * @returns {(Array|string)} Return string if ```withUnit``` is true. Otherwise is an array of ```[value, unit, separator]```
423
461
  */
424
462
  formatByType = (type, value, dataType, options = {}) => {
425
- const { defaultsDeep } = this.lib.aneka
463
+ const { defaultsDeep } = this.app.lib.aneka
426
464
  const { format } = this.getUnitFormat(options)
427
465
  const { withUnit = true } = options
428
466
  const lang = options.lang ?? this.config.lang
@@ -440,7 +478,7 @@ class Bajo extends BasePlugin {
440
478
  *
441
479
  * @method
442
480
  * @param {any} value - Value to format
443
- * @param {string} [type] - Data type to use. See {@link DataType} for acceptable values. If not provided, return the untouched value
481
+ * @param {string} [type] - Data type to use. See {@link TBajoDataType} for acceptable values. If not provided, return the untouched value
444
482
  * @param {Object} [options={}] - Options
445
483
  * @param {string} [options.emptyValue=''] - Empty value to use if function resulted empty. Defaults to the one from your config
446
484
  * @param {boolean} [options.withUnit=true] - Return with its unit appended
@@ -450,7 +488,7 @@ class Bajo extends BasePlugin {
450
488
  * @returns {string} Formatted value
451
489
  */
452
490
  format = (value, type, options = {}) => {
453
- const { defaultsDeep } = this.lib.aneka
491
+ const { defaultsDeep } = this.app.lib.aneka
454
492
  const { format } = this.config.intl
455
493
  const { emptyValue = format.emptyValue } = options
456
494
  const lang = options.lang ?? this.config.lang
@@ -461,7 +499,7 @@ class Bajo extends BasePlugin {
461
499
  if (value instanceof Date) type = 'datetime'
462
500
  }
463
501
  if (['float', 'double'].includes(type) && this.app.bajoSpatial) {
464
- const { latToDms, lngToDms } = this.app.bajoSpatial.lib.anekaSpatial
502
+ const { latToDms, lngToDms } = this.app.lib.anekaSpatial
465
503
  if (options.latitude) return latToDms(value)
466
504
  if (options.longitude) return lngToDms(value)
467
505
  }
@@ -594,7 +632,7 @@ class Bajo extends BasePlugin {
594
632
  *
595
633
  * @method
596
634
  * @param {string} pkgName - Package name to find
597
- * @param {*} base - Provide base name if ```pkgName``` is a module under ```base```'s package name
635
+ * @param {string} base - Provide base name if ```pkgName``` is a module under ```base```'s package name
598
636
  * @returns {string} Return absolute package directory
599
637
  */
600
638
  getModuleDir = (pkgName, base) => {
@@ -622,7 +660,7 @@ class Bajo extends BasePlugin {
622
660
  */
623
661
  getPluginDataDir = (name, ensureDir = true) => {
624
662
  const plugin = this.getPlugin(name)
625
- const dir = `${this.app.bajo.dir.data}/plugins/${plugin.name}`
663
+ const dir = `${this.app.bajo.dir.data}/plugins/${plugin.ns}`
626
664
  if (ensureDir) fs.ensureDirSync(dir)
627
665
  return dir
628
666
  }
@@ -631,7 +669,7 @@ class Bajo extends BasePlugin {
631
669
  * Resolve file path from:
632
670
  *
633
671
  * - local/absolute file
634
- * - ns based path (```myPlugin:/path/to/file.txt```)
672
+ * - TNsPath (```myPlugin:/path/to/file.txt```)
635
673
  *
636
674
  * @method
637
675
  * @param {string} file - File path, see above for supported types
@@ -673,46 +711,38 @@ class Bajo extends BasePlugin {
673
711
  if (silent) return false
674
712
  throw this.error('pluginWithNameAliasNotLoaded%s', name)
675
713
  }
676
- name = plugin.name
714
+ name = plugin.ns
677
715
  }
678
716
  return this.app[name]
679
717
  }
680
718
 
681
719
  /**
682
- * Import file/module from any loaded plugins
720
+ * Import file/module from any loaded plugins.
683
721
  *
684
- * Your plugin structure:
685
- * ```
686
- * |- src
687
- * | |- lib
688
- * | | |- my-module.js
689
- * |- index.js
690
- * |- package.json
691
- * ```
722
+ * Method proxy from {@link module:Lib.importModule}
692
723
  *
693
- * Inside your app/plugin:
694
- * ```javascript
695
- * const { importModule } = this.app.bajo
696
- * const myModule = await importModule('myPlugin:/src/lib/my-module.js')
697
- * ```
698
724
  * @method
699
725
  * @async
700
- * @param {string} file - File in format ```ns:<ns based file path>```
701
- * @param {Object} [options={}] - Options
702
- * @param {boolean} [options.asDefaultImport=true] - If ```true``` (default), return default imported module
703
- * @param {boolean} [options.asHandler] - If ```true```, return as a {@link HandlerType|handler}
704
- * @param {boolean} [options.noCache] - If ```true```, always import as a fresh copy
705
- * @returns {(function|Object)}
726
+ * @see module:Lib.importModule
706
727
  */
707
728
  importModule = async (file, { asDefaultImport, asHandler, noCache } = {}) => {
708
729
  return await importModule.call(this, file, { asDefaultImport, asHandler, noCache })
709
730
  }
710
731
 
711
732
  /**
712
- * Import one or more package belongs to a plugin
733
+ * Import one or more packages belongs to a plugin.
734
+ *
735
+ * If the last arguments passed is an object, this object serves as options object:
736
+ * - ```returnDefault```: should return package's default export. Defaults to ```true```
737
+ * - ```throwNotFound```: should throw if package is not found. Defaults to ```false```
738
+ * - ```noCache```: always use fresh import. Defaults to ```false```
739
+ * - ```asObject```: see below. Defaults to ```false```
713
740
  *
714
- * Example: you want to import packages ```delay``` and ```chalk``` from ```bajo``` namespace and use it inside your app/plugin
741
+ * Return value:
742
+ * - if ```options.asObject``` is ```true``` (default ```false```), return as object with package's names as it's keys
743
+ * - Otherwise depends on how many parameters are provided, it should return the named package or an array of packages
715
744
  *
745
+ * Example: you want to import ```delay``` and ```chalk``` from ```bajo``` plugin because you want to use it in your code
716
746
  * ```javascript
717
747
  * const { importPkg } from this.app.bajo
718
748
  * const [delay, chalk] = await importPkg('bajo:delay', 'bajo:chalk')
@@ -723,14 +753,14 @@ class Bajo extends BasePlugin {
723
753
  *
724
754
  * @method
725
755
  * @async
726
- * @param {...any} pkgs - One or more packages in format ```ns:packageName```
727
- * @returns {(Object|Array)} Depends on how many parameters are provided, it should return the named package or an array of packages
756
+ * @param {...TNsPathPairs} pkgs - One or more packages in format ```{ns}:{packageName}```
757
+ * @returns {(Object|Array)} See above
728
758
  */
729
759
  importPkg = async (...pkgs) => {
730
- const { defaultsDeep } = this.lib.aneka
760
+ const { defaultsDeep } = this.app.lib.aneka
731
761
  const result = {}
732
762
  const notFound = []
733
- let opts = { returnDefault: true, thrownNotFound: false }
763
+ let opts = { returnDefault: true, throwNotFound: false }
734
764
  if (isPlainObject(last(pkgs))) {
735
765
  opts = defaultsDeep(pkgs.pop(), opts)
736
766
  }
@@ -742,7 +772,7 @@ class Bajo extends BasePlugin {
742
772
  notFound.push(pkg)
743
773
  continue
744
774
  }
745
- const p = this.readJson(`${dir}/package.json`, opts.thrownNotFound)
775
+ const p = this.readJson(`${dir}/package.json`, opts.throwNotFound)
746
776
  const mainFileOrg = dir + '/' + (p.main ?? get(p, 'exports.default', 'index.js'))
747
777
  let mainFile = resolvePath(mainFileOrg, os.platform() === 'win32')
748
778
  if (isEmpty(path.extname(mainFile))) {
@@ -758,17 +788,17 @@ class Bajo extends BasePlugin {
758
788
  result[name] = mod
759
789
  }
760
790
  if (notFound.length > 0) throw this.error('cantFind%s', this.join(notFound))
761
- if (pkgs.length === 1) return result[keys(result)[0]]
762
791
  if (opts.asObject) return result
792
+ if (pkgs.length === 1) return result[keys(result)[0]]
763
793
  return values(result)
764
794
  }
765
795
 
766
796
  /**
767
- * Check whether directory is empty or not. More info please {@link https://github.com/gulpjs/empty-dir|check here}.
797
+ * Check whether a directory is empty or not. More info please {@link https://github.com/gulpjs/empty-dir|check here}.
768
798
  *
769
799
  * @method
770
800
  * @async
771
- * @param {string} dir - Directory to check. Can be a ns based directory too!
801
+ * @param {(string|TNsPathPairs)} dir - Directory to check
772
802
  * @param {function} filterFn - Filter function to filter out files that cause false positives.
773
803
  * @returns {boolean}
774
804
  */
@@ -782,7 +812,7 @@ class Bajo extends BasePlugin {
782
812
  * Check whether log level is within log's app current level
783
813
  *
784
814
  * @method
785
- * @param {string} level - Level to check. See {@link LogLevelsType} for more
815
+ * @param {string} level - Level to check. See {@link TLogLevels} for more
786
816
  * @returns {boolean}
787
817
  */
788
818
  isLogInRange = (level) => {
@@ -831,19 +861,28 @@ class Bajo extends BasePlugin {
831
861
  return this.isValidAppPlugin(dir, 'plugin', returnPkg)
832
862
  }
833
863
 
834
- join = (array, sep) => {
835
- const { isSet } = this.lib.aneka
864
+ /**
865
+ * Human friendly join array of items.
866
+ *
867
+ * @method
868
+ * @param {any[]} array - Array to join
869
+ * @param {(string|Object)} options - If provided and is a string, it will be used as separator
870
+ * @param {string} [options.separator=', '] - Separator to use
871
+ * @param {string} [options.lastSeparator=and] - Text to use as the last separator
872
+ * @returns {string}
873
+ */
874
+ join = (array, options) => {
875
+ const { isSet } = this.app.lib.aneka
836
876
  const translate = val => {
837
- if (this && this.print) return this.print.write(val).toLowerCase()
838
- return val
877
+ return this.t(val).toLowerCase()
839
878
  }
840
879
  if (array.length === 0) return translate('none')
841
880
  if (array.length === 1) return array[0]
842
- if (isSet(sep) && !isPlainObject(sep)) return array.join(sep)
843
- let { separator = ', ', joiner = 'and' } = sep ?? {}
844
- joiner = translate(joiner)
881
+ if (isSet(options) && !isPlainObject(options)) return array.join(options)
882
+ let { separator = ', ', lastSeparator = 'and' } = options ?? {}
883
+ lastSeparator = translate(lastSeparator)
845
884
  const last = (array.pop() ?? '').trim()
846
- return array.map(a => (a + '').trim()).join(separator) + ` ${joiner} ${last}`
885
+ return array.map(a => (a + '').trim()).join(separator) + ` ${lastSeparator} ${last}`
847
886
  }
848
887
 
849
888
  /**
@@ -864,22 +903,24 @@ class Bajo extends BasePlugin {
864
903
  * Parse duration to its millisecond value. Use {@link https://github.com/vercel/ms|ms} under the hood
865
904
  *
866
905
  * @method
867
- * @param {(number|string)} dur - If string is given, parse this to its millisecond value. Otherwise return as is
906
+ * @param {(number|string)} dur - If string is given, parse this to its millisecond value. Otherwise returns as is
868
907
  * @returns {number}
908
+ * @see {@link https://github.com/vercel/ms|ms}
869
909
  */
870
910
  parseDur = (dur) => {
871
911
  return isNumber(dur) ? dur : ms(dur)
872
912
  }
873
913
 
874
914
  /**
875
- * Parse datetime string as Javascript object. Please visit {@link https://day.js.org|dayjs} for valid formats and more infos
915
+ * Parse datetime string as Javascript date object. Please visit {@link https://day.js.org|dayjs} for valid formats and more infos
876
916
  *
877
917
  * @method
878
918
  * @param {string} dt - Datetime string
879
- * @returns {Object} Javascript object
919
+ * @returns {Object} Javascript date object
920
+ * @see {@link https://day.js.org|dayjs}
880
921
  */
881
922
  parseDt = (dt) => {
882
- const value = this.lib.dayjs(dt)
923
+ const value = this.app.lib.dayjs(dt)
883
924
  if (!value.isValid()) throw this.error('dtUnparsable%s', dt)
884
925
  return value.toDate()
885
926
  }
@@ -889,7 +930,7 @@ class Bajo extends BasePlugin {
889
930
  * to parse values, so please have a visit to know how it works
890
931
  *
891
932
  * If ```options.parseValue``` is ```true```, any key ends with ```Dur``` and ```Dt``` will
892
- * also be parsed as millisecond and Javascript datetime accordingly
933
+ * also be parsed as millisecond and Javascript date time accordingly.
893
934
  *
894
935
  * @method
895
936
  * @param {(Object|string)} input - If string is given, parse it first using JSON.parse
@@ -898,14 +939,15 @@ class Bajo extends BasePlugin {
898
939
  * @param {boolean} [options.parseValue=false] - If ```true```, values will be parsed & normalized
899
940
  * @param {string} [options.lang] - If provided, use this language instead of the one in config
900
941
  * @returns {Object}
942
+ * @see {@link https://github.com/ladjs/dotenv-parse-variables}
901
943
  */
902
944
  parseObject = (input, options = {}) => {
903
945
  const { silent = true, parseValue = false, lang, ns } = options
904
- const { isSet } = this.lib.aneka
946
+ const { isSet } = this.app.lib.aneka
905
947
  const translate = (item) => {
906
948
  const scope = ns ? this.app[ns] : this
907
949
  const [text, ...args] = item.split('|')
908
- return scope.print.write(text, ...args, { lang })
950
+ return scope.t(text, ...args, { lang })
909
951
  }
910
952
  const statics = ['*']
911
953
  if (isString(input)) {
@@ -955,7 +997,7 @@ class Bajo extends BasePlugin {
955
997
  }
956
998
 
957
999
  pick = (obj, items, excludeUnset) => {
958
- const { isSet } = this.lib.aneka
1000
+ const { isSet } = this.app.lib.aneka
959
1001
  const result = {}
960
1002
  for (const item of items) {
961
1003
  const [k, nk] = item.split(':')
@@ -984,25 +1026,25 @@ class Bajo extends BasePlugin {
984
1026
  * @returns {Object}
985
1027
  */
986
1028
  readConfig = async (file, { ns, pattern, globOptions = {}, ignoreError, defValue = {}, opts = {} } = {}) => {
987
- if (!ns) ns = this.name
1029
+ if (!ns) ns = this.ns
988
1030
  file = resolvePath(this.getPluginFile(file))
989
1031
  let ext = path.extname(file)
990
1032
  const fname = path.dirname(file) + '/' + path.basename(file, ext)
991
1033
  ext = ext.toLowerCase()
992
1034
  if (ext === '.js') {
993
- const { readHandler } = find(this.app.bajo.configHandlers, { ext })
1035
+ const { readHandler } = find(this.app.configHandlers, { ext })
994
1036
  return this.parseObject(await readHandler.call(this.app[ns], file, opts))
995
1037
  }
996
- if (ext === '.json') return await this.readJson(file)
1038
+ if (ext === '.json') return await this.fromJson(file, null)
997
1039
  if (!['', '.*'].includes(ext)) {
998
- const item = find(this.app.bajo.configHandlers, { ext })
1040
+ const item = find(this.app.configHandlers, { ext })
999
1041
  if (!item) {
1000
1042
  if (!ignoreError) throw this.error('cantParse%s', file, { code: 'BAJO_CONFIG_NO_PARSER' })
1001
1043
  return this.parseObject(defValue)
1002
1044
  }
1003
1045
  return this.parseObject(await item.readHandler.call(this.app[ns], file, opts))
1004
1046
  }
1005
- const item = pattern ?? `${fname}.{${map(map(this.app.bajo.configHandlers, 'ext'), k => k.slice(1)).join(',')}}`
1047
+ const item = pattern ?? `${fname}.{${map(map(this.app.configHandlers, 'ext'), k => k.slice(1)).join(',')}}`
1006
1048
  const files = await fastGlob(item, globOptions)
1007
1049
  if (files.length === 0) {
1008
1050
  if (!ignoreError) throw this.error('noConfigFileFound', { code: 'BAJO_CONFIG_FILE_NOT_FOUND' })
@@ -1011,12 +1053,12 @@ class Bajo extends BasePlugin {
1011
1053
  let config = defValue
1012
1054
  for (const f of files) {
1013
1055
  const ext = path.extname(f).toLowerCase()
1014
- const item = find(this.app.bajo.configHandlers, { ext })
1056
+ const item = find(this.app.configHandlers, { ext })
1015
1057
  if (!item) {
1016
1058
  if (!ignoreError) throw this.error('cantParse%s', f, { code: 'BAJO_CONFIG_NO_PARSER' })
1017
1059
  continue
1018
1060
  }
1019
- config = await item.readHandler.call(this.app[ns], f, opts)
1061
+ config = await item.readHandler.call(this.app[ns], f, null, opts)
1020
1062
  if (!isEmpty(config)) break
1021
1063
  }
1022
1064
  return this.parseObject(config)
@@ -1032,7 +1074,7 @@ class Bajo extends BasePlugin {
1032
1074
  */
1033
1075
  readJson = (file, thrownNotFound = false) => {
1034
1076
  if (isPlainObject(thrownNotFound)) thrownNotFound = false
1035
- if (!fs.existsSync(file) && thrownNotFound) throw this.error('notFound%s%s', this.print.write('file'), file)
1077
+ if (!fs.existsSync(file) && thrownNotFound) throw this.error('notFound%s%s', this.t('file'), file)
1036
1078
  let resp
1037
1079
  try {
1038
1080
  resp = fs.readFileSync(file, 'utf8')
@@ -1041,12 +1083,49 @@ class Bajo extends BasePlugin {
1041
1083
  return this.parseObject(JSON.parse(resp))
1042
1084
  }
1043
1085
 
1086
+ fromJson (file, isContent) {
1087
+ const content = isContent ? file : fs.readFileSync(file, 'utf8')
1088
+ return JSON.parse(content)
1089
+ }
1090
+
1091
+ toJson = (file, isContent, opts = 2) => {
1092
+ const content = isContent ? file : JSON.parse(fs.readFileSync(file, 'utf8'))
1093
+ return JSON.stringify(content, null, opts)
1094
+ }
1095
+
1096
+ /**
1097
+ * Read all config files by path
1098
+ *
1099
+ * @method
1100
+ * @async
1101
+ * @param {string} path - Base path to start looking config files
1102
+ * @returns {Object}
1103
+ */
1104
+ readAllConfigs = async (path) => {
1105
+ const { defaultsDeep } = this.app.lib.aneka
1106
+ let cfg = {}
1107
+ let ext = {}
1108
+ // default config file
1109
+ try {
1110
+ cfg = await this.readConfig(`${path}.*`, { ignoreError: true })
1111
+ } catch (err) {
1112
+ if (['BAJO_CONFIG_NO_PARSER'].includes(err.code)) throw err
1113
+ }
1114
+ // env based config file
1115
+ try {
1116
+ ext = await this.readConfig(`${path}-${this.config.env}.*`, { ignoreError: true })
1117
+ } catch (err) {
1118
+ if (!['BAJO_CONFIG_FILE_NOT_FOUND'].includes(err.code)) throw err
1119
+ }
1120
+ return defaultsDeep({}, ext, cfg)
1121
+ }
1122
+
1044
1123
  /**
1045
- * Run named hook
1124
+ * Run named hook/event
1046
1125
  *
1047
1126
  * @method
1048
1127
  * @async
1049
- * @param {string} hookName - ns based hook name
1128
+ * @param {TNsPathPairs} hookName
1050
1129
  * @param {...any} [args] - Argument passed to the hook function
1051
1130
  * @returns {Array} Array of hook execution results
1052
1131
  */
@@ -1085,7 +1164,7 @@ class Bajo extends BasePlugin {
1085
1164
  */
1086
1165
  saveAsDownload = async (file, item, printSaved = true) => {
1087
1166
  const { print, getPluginDataDir } = this.app.bajo
1088
- const fname = increment(`${getPluginDataDir(this.name)}/download/${trim(file, '/')}`, { fs: true })
1167
+ const fname = increment(`${getPluginDataDir(this.ns)}/download/${trim(file, '/')}`, { fs: true })
1089
1168
  const dir = path.dirname(fname)
1090
1169
  if (!fs.existsSync(dir)) fs.ensureDirSync(dir)
1091
1170
  await fs.writeFile(fname, item, 'utf8')