bajo 2.23.0 → 2.24.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 (108) hide show
  1. package/.jsdoc.conf.json +2 -1
  2. package/class/app.js +220 -107
  3. package/class/bajo.js +246 -174
  4. package/class/base.js +85 -24
  5. package/class/cache.js +8 -16
  6. package/class/err.js +31 -10
  7. package/class/log.js +46 -38
  8. package/class/plugin.js +53 -30
  9. package/class/print.js +18 -15
  10. package/class/tools.js +6 -5
  11. package/docs/App.html +51 -1
  12. package/docs/Bajo.html +1 -1
  13. package/docs/Base.html +34 -1
  14. package/docs/Cache.html +1 -1
  15. package/docs/Err.html +2 -2
  16. package/docs/Log.html +2 -2
  17. package/docs/Main.html +3 -0
  18. package/docs/Plugin.html +1 -1
  19. package/docs/Print.html +1 -1
  20. package/docs/Tools.html +1 -1
  21. package/docs/class_app.js.html +222 -109
  22. package/docs/class_bajo.js.html +260 -179
  23. package/docs/class_base.js.html +87 -26
  24. package/docs/class_cache.js.html +10 -18
  25. package/docs/class_err.js.html +33 -12
  26. package/docs/class_log.js.html +48 -40
  27. package/docs/class_plugin.js.html +55 -32
  28. package/docs/class_print.js.html +20 -17
  29. package/docs/class_tools.js.html +8 -7
  30. package/docs/data/search.json +1 -1
  31. package/docs/extend_bajo_hook.js.html +289 -0
  32. package/docs/global.html +22 -1
  33. package/docs/index.html +1 -1
  34. package/docs/index.js.html +53 -16
  35. package/docs/lib_applet-hook.js.html +328 -0
  36. package/docs/lib_config.js.html +167 -0
  37. package/docs/lib_helper.js.html +186 -192
  38. package/docs/module-Applet.html +3 -0
  39. package/docs/module-Helper-Main.html +3 -0
  40. package/docs/module-Helper.html +1 -8
  41. package/docs/module-Hook.html +1 -1
  42. package/docs/tutorial-01_WELCOME.html +3 -0
  43. package/docs/tutorial-02_GETTING_STARTED.html +125 -0
  44. package/docs/tutorial-03_USER_GUIDE.html +54 -0
  45. package/docs/tutorial-04_DEV_GUIDE.html +178 -0
  46. package/docs/tutorial-05_ECOSYSTEM.html +3 -0
  47. package/extend/bajo/intl/en-US.json +2 -1
  48. package/extend/bajo/intl/id.json +2 -1
  49. package/index.js +51 -14
  50. package/lib/applet-hook.js +325 -0
  51. package/lib/config.js +164 -0
  52. package/lib/helper.js +184 -190
  53. package/package.json +6 -2
  54. package/test/00-global-hooks.test.js +44 -0
  55. package/test/e2e/_run.js +19 -0
  56. package/test/e2e/e2e-applet-process.test.js +66 -0
  57. package/test/e2e/e2e-boot-process.test.js +62 -0
  58. package/test/e2e/e2e-hooks-process.test.js +68 -0
  59. package/test/integration/_setup.js +55 -0
  60. package/test/integration/aspect-01-boot-instance.test.js +24 -0
  61. package/test/integration/aspect-02-plugin-loading.test.js +24 -0
  62. package/test/integration/aspect-03-config-merge.test.js +27 -0
  63. package/test/integration/aspect-04-intl-loading.test.js +29 -0
  64. package/test/integration/aspect-05-cache-roundtrip.test.js +26 -0
  65. package/test/integration/aspect-06-log-file-save.test.js +28 -0
  66. package/test/integration/aspect-07-plugin-data-dir.test.js +25 -0
  67. package/test/integration/aspect-08-plugin-lookup.test.js +25 -0
  68. package/test/integration/aspect-09-config-formats.test.js +26 -0
  69. package/test/integration/aspect-10-hooks.test.js +38 -0
  70. package/test/unit/app.test.js +115 -0
  71. package/test/unit/applet-hook.test.js +10 -0
  72. package/test/unit/bajo.test.js +151 -0
  73. package/test/unit/base.test.js +90 -0
  74. package/test/unit/cache.test.js +89 -0
  75. package/test/unit/config.test.js +14 -0
  76. package/test/unit/err.test.js +57 -0
  77. package/test/unit/helper.test.js +116 -0
  78. package/test/unit/index.test.js +40 -0
  79. package/test/unit/log.test.js +84 -0
  80. package/test/unit/plugin.test.js +95 -0
  81. package/test/unit/print.test.js +72 -0
  82. package/test/unit/tools.test.js +29 -0
  83. package/wiki/CHANGES.md +4 -0
  84. package/wiki/tutorials/01_WELCOME.md +18 -0
  85. package/wiki/tutorials/02_GETTING_STARTED.md +366 -0
  86. package/wiki/tutorials/03_USER_GUIDE.md +262 -0
  87. package/wiki/tutorials/04_DEV_GUIDE.md +386 -0
  88. package/wiki/tutorials/05_ECOSYSTEM.md +95 -0
  89. package/wiki/tutorials/tutorials.json +17 -0
  90. package/lib/hook.js +0 -228
  91. package/test/app.test.js +0 -183
  92. package/test/bajo.test.js +0 -124
  93. package/test/base.test.js +0 -75
  94. package/test/cache.test.js +0 -94
  95. package/test/e2e.test.js +0 -137
  96. package/test/err.test.js +0 -73
  97. package/test/helper.test.js +0 -39
  98. package/test/import-module.test.js +0 -138
  99. package/test/integration.test.js +0 -218
  100. package/test/log.test.js +0 -119
  101. package/test/plugin.test.js +0 -116
  102. package/test/print.test.js +0 -100
  103. package/test/tools.test.js +0 -38
  104. package/wiki/CONFIG.md +0 -36
  105. package/wiki/DEV_GUIDE.md +0 -3
  106. package/wiki/ECOSYSTEM.md +0 -93
  107. package/wiki/GETTING-STARTED.md +0 -356
  108. package/wiki/USER-GUIDE.md +0 -256
package/.jsdoc.conf.json CHANGED
@@ -6,13 +6,14 @@
6
6
  "verbose": true,
7
7
  "destination": "./docs",
8
8
  "template": "node_modules/clean-jsdoc-theme",
9
+ "tutorials": "./wiki/tutorials",
9
10
  "readme": "./docs/static/home.md",
10
11
  "theme_opts": {
11
12
  "default_theme": "light",
12
13
  "display-module-header": true,
13
14
  "title": "Bajo API",
14
15
  "homepageTitle": "Bajo API",
15
- "sections": ["Classes", "Events", "Modules", "Externals", "Global"],
16
+ "sections": ["Tutorials", "Classes", "Events", "Modules", "Externals", "Global"],
16
17
  "menu": [{
17
18
  "title": "NPM",
18
19
  "link": "https://www.npmjs.com/package/bajo"
package/class/app.js CHANGED
@@ -31,100 +31,186 @@ function getCallerFilename () {
31
31
  }
32
32
 
33
33
  /**
34
+ * Supported environments.
35
+ *
36
+ * Environment is one of the most important aspect of a Bajo app. It is used to determine how your app should behave
37
+ * in different environments. It allows you to have different configurations and settings,
38
+ * and as a plugin developer, you can also use it to determine how your plugin should behave in different environments.
39
+ *
34
40
  * @typedef {Object} TEnv
35
41
  * @memberof App
36
42
  * @property {string} dev=development
43
+ * @property {string} stag=staging
37
44
  * @property {string} prod=production
45
+ * @property {string} test=testing
38
46
  */
39
47
 
40
48
  /**
49
+ * Your read handler must follow this structure. It is used to read and parse config file in a particular format.
50
+ *
41
51
  * @callback readHandler
42
52
  * @memberof App
43
- * @param {string} text - Text to be parsed
44
- * @param {object} options - Options object
53
+ * @async
54
+ * @param {string} text - Text to be parsed or file path to be read if `options.readFromFile` is `true`
55
+ * @param {object|boolean} [options={}] - Options object. If a boolean is provided, it will be treated as `options.readFromFile`
56
+ * @param {boolean} [options.readFromFile=false] - If `true`, `text` is treated as a file path
57
+ * @param {boolean} [options.throwNotFound=false] - If `true`, throw exception if file is not found
58
+ * @param {object} [options.parserOpts={}] - Options to be passed to the parser
45
59
  * @returns {object} Parsed object
46
60
  */
47
61
 
48
62
  /**
63
+ * Your write handler must follow this structure. It is used to write config file in a particular format.
64
+ *
49
65
  * @callback writeHandler
66
+ * @async
50
67
  * @memberof App
51
68
  * @param {Object} data - Data to be stringified
52
- * @param {Object} [options={}] - Options
53
- * @param {boolean} [options.writeToFile=false] - If true, write result to a file
54
- * @param {string} [options.saveAsFile] - The file path to save result if writeToFile is true
69
+ * @param {Object|string} [options={}] - Options object. If a string is provided, it will be treated as `options.writeToFile`
70
+ * @param {string} [options.writeToFile] - If not empty, write result to this file path instead of returning it as string
71
+ * @param {object} [options.parserOpts={}] - Options to be passed to the parser
55
72
  * @returns {string} Stringified result
56
73
  */
57
74
 
58
75
  /**
76
+ * Config handler definition. Your own handler must follow this structure.
77
+ *
59
78
  * @typedef {Object} TConfigHandler
60
79
  * @memberof App
61
- * @property {string} ns - Namespace
62
- * @property {string} ext - File extension
63
- * @property {App.readHandler} [readHandler] - Function to call for reading
64
- * @property {App.writeHandler} [writeHandler] - Function to call for writing
80
+ * @property {string} ns - Owner plugin namespace
81
+ * @property {string} ext - Supported file extension
82
+ * @property {App.readHandler} [readHandler] - Async function to call for reading
83
+ * @property {App.writeHandler} [writeHandler] - Async function to call for writing
84
+ * @see {@link App#configHandlers}
65
85
  */
66
86
 
67
87
  /**
88
+ * Options object passed to {@link App} constructor. By default, you don't need to pass any options,
89
+ * unless you want to manually override the default behavior.
68
90
  * @typedef {Object} TOptions
69
91
  * @memberof App
70
92
  * @property {string} [cwd] - Set current working directory. Defaults to the script directory
71
- * @property {string[]} [plugins] - Array of plugins to load. If provided, it override the list in `package.json` and `.plugins` file
72
- * @property {Object} [config] - Plugin's config object. If provided, plugin configs will no longer be read from its config files
93
+ * @property {string[]} [plugins] - Array of plugins **package names** to load. If provided, it override the list in `package.json` and `.plugins` file
94
+ * @property {Object} [config] - Plugin config objects, with plugin name as keys and their respective config objects as values. If provided, plugin configs will no longer be read from its config files
95
+ * @property {module:Hook.THook[]} [hooks] - Array of hooks to be added to the app
96
+ * @example
97
+ * // If you feel adventurous and decide to manually boot your app without the default boot module, here we go,,,
98
+ * import App from 'bajo/class/app.js'
99
+ *
100
+ * const options = {
101
+ * plugins: ['my-plugin', 'my-other-plugin'],
102
+ * config: {
103
+ * myPlugin: {
104
+ * // plugin config here
105
+ * },
106
+ * myOtherPlugin: {
107
+ * // plugin config here
108
+ * }
109
+ * },
110
+ * hooks: [{
111
+ * name: 'myPlugin:myHook',
112
+ * handler: async function (arg1, arg2) {
113
+ * // do something with arg1 and arg2
114
+ * }
115
+ * }]
116
+ * }
117
+ * }
118
+ *
119
+ * const app = new App(options)
73
120
  */
74
121
 
75
122
  /**
76
123
  * App class. This is the root. This is where all plugins call it home.
77
124
  *
125
+ * This class should not be instantiated directly (see example in {@link App.TOptions} if you
126
+ * feel adventurous). Instead, use the default {@link boot|boot} script.
127
+ *
128
+ * Plugins (including special plugins {@link Main} and {@link Bajo}) are loaded and attached as
129
+ * property members of this class. They can be accessed using their name/namespace.
130
+ *
131
+ * In every plugin there is a reference to this class instance, so they can call each other easily,
132
+ * e.g. in your plugin method, you would call other plugin's method like this `this.app.otherPlugin.method()`.
133
+ *
134
+ * A typical Bajo app should follow the following structure:
135
+ * ```
136
+ * my-app
137
+ * |-- package.json
138
+ * |-- index.js
139
+ * |-- main
140
+ * | |-- extend
141
+ * | | |-- bajo
142
+ * | | | |-- hook
143
+ * | | | | | |-- myPlugin:beforeAction.js
144
+ * | | | | | |-- myOtherPlugin:afterAction.js
145
+ * | | | |-- intl
146
+ * | |-- index.js
147
+ * |-- data
148
+ * | |-- config
149
+ * | | |-- .plugins
150
+ * | | |-- bajo.yml
151
+ * | | |-- myPlugin.yml
152
+ * ```
153
+ *
78
154
  * @class
79
155
  */
80
156
  class App {
81
157
  /**
82
- * Constructor.
158
+ * Constructor. See {@link App.TOptions} if you want to write custom boot process for your app without using the default boot module.
83
159
  * @param {App.TOptions} [options={}] - Options object.
84
160
  */
85
161
  constructor (options = {}) {
86
162
  /**
87
- * Copy of provided options.
163
+ * Copy of the provided options. See {@link App.TOptions} for details.
88
164
  * @type {App.TOptions}
89
165
  */
90
166
  this.options = options
91
167
 
92
168
  /**
93
- * Main namespace.
169
+ * App's main plugin namespace (`main`). Read-only
94
170
  * @type {string}
95
171
  */
96
172
  this.mainNs = 'main'
97
173
 
98
174
  /**
99
- * App environments.
175
+ * Supported environments. Read-only
100
176
  * @type {App.TEnv}
101
177
  */
102
- this.envs = { dev: 'development', prod: 'production' }
178
+ this.envs = { dev: 'development', prod: 'production', stag: 'staging', test: 'testing' }
103
179
 
104
180
  /**
105
- * Date/time when your app start.
181
+ * Date/time when your app start. You can use this e.g. to calculate how long your app has been running. Read-only
106
182
  * @type {Date}
107
183
  */
108
184
  this.runAt = new Date()
109
185
 
110
186
  /**
111
- * Applets container.
187
+ * Applets container. Usefull to get all available applets in your app. See {@link module:Applet|applet} for details.
112
188
  * @type {Array}
113
189
  */
114
190
  this.applets = []
115
191
 
116
192
  /**
117
- * Plugin's package names container. This is the list of plugins to load.
193
+ * Original plugin's **package names** (not to be confused with plugin namespaces) container. Ypu can use this
194
+ * information to get the real package name of a plugin.
195
+ *
196
+ * > **Reminder**: In Bajo, plugin name/namespace is the camel-cased version of its package name.
197
+ * E.g. `my-plugin` → `myPlugin`.
198
+ *
118
199
  * @type {string[]}
119
200
  */
120
201
  this.pluginPkgs = options.plugins ?? []
121
202
 
122
203
  /**
123
- * Config handlers.
204
+ * Config handlers container. This is where all config handlers are stored. Each handler
205
+ * is responsible to read and write config file in a particular format. Bajo uses this heavily to read/write config
206
+ * files and/or anything that needs to be parsed/stringified from string to object and vice versa.
124
207
  *
125
208
  * By default, there are three built-in handlers: `.js`, `.json`
126
209
  * and `.yml/.yaml`. Use plugins to add more, e.g {@link https://github.com/ardhi/bajo-config|bajo-config}
127
210
  * lets you to use `.toml`.
211
+ *
212
+ * > *Note*: `.js` is for reading only, it cannot be used to write config file.
213
+
128
214
  * @type {Array<App.TConfigHandler>}
129
215
  */
130
216
  this.configHandlers = []
@@ -143,55 +229,66 @@ class App {
143
229
  this.lib.parseObject = this.lib.parseObject.bind(this)
144
230
 
145
231
  /**
146
- * Instance of system log.
147
- *
232
+ * Instance of the system log. This will automatically be instantiated early during boot process. See {@link Log} for details.
148
233
  * @type {Log}
149
234
  */
150
235
  this.log = {}
151
236
 
152
237
  /**
153
- * All plugin's base class are saved here as key-value pairs with plugin name as its key.
154
- * The special key `Base` && `Tools` is for {@link Base} & {@link Tools} class so that anytime you want to
155
- * create your own plugin, you can just write something like this:
238
+ * All plugin's base classes are saved here for reference so that you can extend them in your own plugins.
239
+ *
240
+ * Two basic classes from Bajo package are also provided: {@link Base} and {@link Tools}. These are the most
241
+ * used classes in Bajo plugin development. You can extend them to create your own plugin's base class:
242
+ *
243
+ * ```javascript
244
+ * // factory function to create your own plugin's base class
245
+ * async function () {
246
+ * const { Base } = this.app.baseClass
247
+ * class MyBase extends Base {
248
+ * constructor(pkgName, app) {
249
+ * super(pkgName, app)
250
+ * this.myProperty = 'myValue' // your class property
251
+ * }
156
252
  *
157
- * `javascript
158
- * class MyPlugin extends this.app.baseClass.Base {
159
- * ... your class
253
+ * async myMethod() {
254
+ * // your class method
255
+ * }
256
+ * }
160
257
  * }
258
+ * ```
161
259
  *
162
260
  * @type {Object}
163
261
  */
164
262
  this.baseClass = { Base, Tools }
165
263
 
166
264
  /**
167
- * If app runs in applet mode, this will be the applet's name.
265
+ * If app runs in **applet** mode, this will be the current applet's name. Otherwise, it will be `undefined`. See {@link module:Applet|applet} for details.
168
266
  * @type {string}
169
267
  */
170
268
  this.applet = undefined
171
269
 
172
270
  /**
173
271
  * Parsed program arguments.
174
- * @type {string[]}
175
- * @see module:Helper.parseArgsArgv
176
- * @example
272
+ *
273
+ * Example:
274
+ * ```js
177
275
  * $ node index.js arg1 arg2
178
276
  * ...
179
277
  * console.log(this.args) // it should print: ['arg1', 'arg2']
278
+ * ```
279
+ *
280
+ * @type {string[]}
180
281
  */
181
282
  this.args = []
182
283
 
183
284
  /**
184
- * Parsed program options.
185
- *
285
+ * Parsed program options:
186
286
  * - Dash (`-`) breaks the string into object keys
187
- * - While colon (`:`) is used as namespace separator. If no namespace found, it is saved under `_` key.
287
+ * - While colon (`:`) is used as namespace - object separator. If no such namespace found, it is saved under `_`.
188
288
  *
189
- * Values are parsed automatically. See {@link https://github.com/ladjs/dotenv-parse-variables|dotenv-parse-variables}
190
- * for details.
289
+ * Parsed object values are normalized to its primitives using {@link https://github.com/ladjs/dotenv-parse-variables|dotenv-parse-variables}
191
290
  *
192
- * @type {Object}
193
- * @see module:Helper.parseArgsArgv
194
- * @example
291
+ * ```js
195
292
  * $ node index.js --my-name-first=John --my-name-last=Doe --my-birthDay=secret --nameSpace:path-subPath=true
196
293
  * ...
197
294
  * // {
@@ -203,37 +300,33 @@ class App {
203
300
  * // },
204
301
  * // nameSpace: { path: { subPath: true } }
205
302
  * // }
303
+ *
304
+ * @type {Object}
206
305
  */
207
306
  this.argv = {}
208
307
 
209
308
  /**
210
- * Environment variables. Support dotenv (`.env`) file too!
211
- *
212
- * - Underscore (`_`) translates key to camel-cased one
213
- * - Double underscores (`__`) breaks the key into object keys
214
- * - While dot (`.`) is used as namespace separator. If no namespace found, it is saved under `_` key.
215
- *
216
- * Values are also parsed automatically using {@link https://github.com/ladjs/dotenv-parse-variables|dotenv-parse-variables}.
309
+ * Parsed environment variables. Support dotenv (`.env`) file too!
310
+ * - Underscore (`_`) between keys merge those keys into a single camel-cased key
311
+ * - Double underscores (`__`) between keys breaks the key into nested objects
312
+ * - Meanwhile a dot (`.`) between keys breaks the key into nested, name-spaced objects
217
313
  *
218
- * E.g.:
314
+ * Parsed object values are normalized to its primitives using {@link https://github.com/ladjs/dotenv-parse-variables|dotenv-parse-variables}
219
315
  *
316
+ * Example:
220
317
  * - `MY_KEY=secret` → `{ _: { myKey: 'secret' } }`
221
318
  * - `MY_KEY__SUB_KEY=supersecret` → `{ _: { myKey: { subKey: 'supersecret' } } }`
222
319
  * - `MY_NS.MY_NAME=John` → `{ myNs: { myName: 'John' } }`
223
320
  *
321
+ * > **Tips**: During boot process, Bajo will add `APPDIR` environment variable which points to your app's root directory.
322
+ *
224
323
  * @type {Object}
225
- * @see module:Helper.parseEnv
226
324
  */
227
325
  this.envVars = {}
228
-
229
- /**
230
- * Placeholder for boxen that will get imported from `bajoCli` later during boot process.
231
- * @type {Object}
232
- */
233
326
  this.boxen = null
234
327
 
235
328
  /**
236
- * Cache instance. This is used to store temporary data in memory for faster access.
329
+ * Internal cache instance. This is used to store temporary data in memory for faster access.
237
330
  * @type {Cache}
238
331
  */
239
332
  this.cache = new Cache(this)
@@ -249,11 +342,12 @@ class App {
249
342
  }
250
343
 
251
344
  /**
252
- * Add and save plugin and it's base class definition (if provided).
345
+ * Add and save plugin and its base class definition (if provided) to the app instance. You can then reference
346
+ * the plugin using its namespace, e.g. `this.myPlugin` or `this['myPlugin']`.
253
347
  *
254
348
  * @method
255
- * @param {TPlugin} plugin - A valid bajo plugin.
256
- * @param {Object} [baseClass] - Base class definition.
349
+ * @param {Base} plugin - A valid Bajo plugin.
350
+ * @param {class} [baseClass] - Base class definition.
257
351
  */
258
352
  addPlugin = (plugin, baseClass) => {
259
353
  if (this[plugin.ns]) throw new Error(`Plugin '${plugin.ns}' added already`)
@@ -262,7 +356,7 @@ class App {
262
356
  }
263
357
 
264
358
  /**
265
- * Get all loaded plugin namespaces.
359
+ * Get all loaded **plugin namespaces**.
266
360
  *
267
361
  * @method
268
362
  * @returns {string[]}
@@ -272,34 +366,36 @@ class App {
272
366
  }
273
367
 
274
368
  /**
275
- * Get loaded plugins.
369
+ * Get loaded plugins by their namespaces. If no namespace is provided, it will return all loaded plugins.
276
370
  *
277
371
  * @method
278
- * @param {string[]} [nss] - Array of namespaces. If empty, it returns all loaded plugins.
279
- * @returns {TPlugin[]}
372
+ * @param {string[]} [nss] - Array of namespaces. If empty, it returns all loaded plugins
373
+ * @param {boolean} [nameOnly=false] - If `true`, it will return only the plugin namespaces instead of the plugin instances
374
+ * @returns {Array<Base>} Array of plugin instances
280
375
  */
281
- getPlugins = (nss) => {
376
+ getPlugins = (nss, nameOnly = false) => {
282
377
  const allNs = nss ?? this.getAllNs()
283
- return allNs.map(ns => this[ns])
378
+ return allNs.map(ns => nameOnly ? ns : this[ns])
284
379
  }
285
380
 
286
381
  /**
287
- * Get all plugins loaded plugins.
382
+ * Get all loaded plugins. Alias to {@link App#getPlugins|getPlugins()} with no namespace provided.
288
383
  *
289
384
  * @method
290
- * @returns {TPlugin[]}
385
+ * @returns {Array<Base>} Array of all loaded plugin instances
291
386
  */
292
387
  getAllPlugins = () => {
293
388
  return this.getPlugins()
294
389
  }
295
390
 
296
391
  /**
297
- * Get plugin by its namespace.
392
+ * Get plugin by its namespace or alias. If the plugin is not loaded, it will throw an error
393
+ * unless `silent` is set to `true`.
298
394
  *
299
395
  * @method
300
- * @param {string} name - Plugin name/namespace or alias.
301
- * @param {boolean} [silent] - If `true`, silently return undefined even on error.
302
- * @returns {Object} Plugin object.
396
+ * @param {string} name - Namespace or alias.
397
+ * @param {boolean} [silent] - If `true`, silently return undefined even on error
398
+ * @returns {Base} Plugin object.
303
399
  */
304
400
  getPlugin = (name, silent) => {
305
401
  if (!this[name]) {
@@ -313,7 +409,7 @@ class App {
313
409
  }
314
410
  }
315
411
  if (!plugin) {
316
- if (silent) return false
412
+ if (silent) return undefined
317
413
  throw this.bajo.error('pluginWithNameAliasNotLoaded%s', name)
318
414
  }
319
415
  name = plugin.ns
@@ -322,11 +418,12 @@ class App {
322
418
  }
323
419
 
324
420
  /**
325
- * Get plugin data directory
421
+ * Get plugin data directory. If the directory does not exist, it will be created automatically
422
+ * unless `ensureDir` is set to `false`.
326
423
  *
327
424
  * @method
328
- * @param {string} name - Plugin name (namespace) or alias.
329
- * @param {boolean} [ensureDir=true] - Set `true` (default) to ensure directory is existed.
425
+ * @param {string} name - Namespace or alias.
426
+ * @param {boolean} [ensureDir=true] - Set `true` (default) to ensure directory exists
330
427
  * @returns {string}
331
428
  */
332
429
  getPluginDataDir = (name, ensureDir = true) => {
@@ -339,14 +436,13 @@ class App {
339
436
 
340
437
  /**
341
438
  * Resolve file path from:
342
- *
343
439
  * - local/absolute file
344
- * - TNsPath (`myPlugin:/path/to/file.txt`)
345
- * - file under node_modules, e.g. `myPlugin:node_modules/some-package/file.txt`
440
+ * - {@link Bajo.TNsPathPairs} e.g. `myPlugin:/path/to/file.txt`
441
+ * - file under `node_modules`, e.g. `myPlugin:node_modules/some-package/file.txt`
346
442
  *
347
443
  * @method
348
- * @param {string} file - File path, see above for supported types.
349
- * @returns {string} Resolved file path.
444
+ * @param {string} file - File path, see above for supported types
445
+ * @returns {string} Resolved file path
350
446
  */
351
447
  getPluginFile = (file) => {
352
448
  const { currentLoc } = this.lib.aneka
@@ -372,13 +468,13 @@ class App {
372
468
  * Dumping variable on screen. Like `console.log` with configurable options. Useful for quick debugging and testing. You can also use it to dump variables in production without worrying about performance because it is using Bajo's built-in cache to store the result of util's inspect, so it will only be processed once for each unique variable.
373
469
  *
374
470
  * Any argument passed to this method will be displayed on screen.
375
- * If the last argument is a boolean `true`, app will quit rightaway after dumping.
471
+ * If the last argument is a string `\q` or `.q`, app will quit rightaway after dumping.
376
472
  *
377
473
  * If you have `bajoCli` plugin installed, variables will be displayed in a nice box using `boxen` package.
378
474
  * Otherwise, it will fallback to `console.log` with util's inspect result.
379
475
  *
380
476
  * To have more control on how the variable is displayed, you can set options in Bajo's config under `dump` key.
381
- * See {@link Bajo#config} for details.
477
+ * See {@link Bajo.TConfig} for details.
382
478
  *
383
479
  * @method
384
480
  * @param {...any} args - Variables to dump.
@@ -386,8 +482,7 @@ class App {
386
482
  dump = (...args) => {
387
483
  let caller = getCallerFilename()
388
484
  caller = caller ? fileURLToPath(caller) : 'Unavailable'
389
- const opts = last(args)
390
- const terminate = isPlainObject(opts) && opts.abort
485
+ const terminate = ['\\q', '.q'].includes(last(args))
391
486
  if (terminate) args.pop()
392
487
  const value = args.length === 1 ? args[0] : args
393
488
  const options = { ...this.bajo.config.dump }
@@ -404,13 +499,15 @@ class App {
404
499
  }
405
500
 
406
501
  /**
407
- * Run application:
502
+ * Run application and begin the boot process. This method is called automatically by {@link boot|boot} script.
408
503
  *
409
- * - Parsing {@link module:Helper.parseArgsArgv|program arguments, options} and {@link module:Helper.parseEnv|environment values}
410
- * - Create {@link Bajo|Bajo} instance & initialize it
411
- * - {@link module:Helper/Bajo.runAsApplet|Run in applet mode} if `-a` or `--applet` is given
504
+ * Boot process includes:
505
+ * - parsing environment values, program arguments, and options
506
+ * - create {@link Bajo|Bajo} instance & initialize it
507
+ * - load all plugins, their hooks, their configs and any other necessary services & resources
508
+ * - {@link Bajo.runAsApplet|run in applet mode} if `-a` or `--applet` is given
412
509
  *
413
- * After boot process is completed, event `bajo:afterBootCompleted` is emitted.
510
+ * After boot process is completed, hook `bajo:afterBoot` is triggered.
414
511
  *
415
512
  * If app mode is `applet`, it runs your choosen applet instead.
416
513
  *
@@ -441,10 +538,10 @@ class App {
441
538
  this.args = args
442
539
  this.argv = parseObject(argv, { parseValue: true })
443
540
  this.envVars = parseObject(parseEnv(), { parseValue: true })
444
- if (get(this, 'envVars._.env') === '[object Object]') set(this, 'envVars._.env', 'dev')
541
+ if (get(this, 'envVars._.env') === '[object Object]') set(this, 'envVars._.env', 'dev') // hack for some env vars that are not parsed correctly
445
542
  this.applet = this.envVars._.applet ?? this.argv._.applet
446
543
  await this.bajo.runHook('bajo:beforeBoot')
447
- await this.bajo.init()
544
+ await this.bajo.bootApp()
448
545
  if (this.bajoCli) this.boxen = await this.bajo.importPkg('bajoCli:boxen')
449
546
  // cache
450
547
  this.cache.purge()
@@ -458,21 +555,25 @@ class App {
458
555
  }
459
556
 
460
557
  /**
461
- * Terminate the app and back to console.
558
+ * Terminate the app forcefully and back to console.
462
559
  *
463
560
  * @method
464
- * @param {string|boolean} [signal=SIGINT] - Signal to send. Set to `true` to terminate immediately without sending any signal.
561
+ * @param {string|boolean} [signal=SIGINT] - Signal to send. Set to `true` to terminate immediately without sending any signal
562
+ * @param {string} [reason] - Reason to be printed on console before exiting
465
563
  */
466
- exit = (signal = 'SIGINT') => {
564
+ exit = (signal = 'SIGINT', reason) => {
565
+ if (reason) console.error(reason)
467
566
  if (signal === true) process.exit('1')
468
567
  process.kill(process.pid, signal)
469
568
  }
470
569
 
471
570
  /**
472
- * Load internationalization & languages files for particular plugin.
571
+ * Load internationalization & languages files for particular plugin by its namespace.
572
+ * It will load all supported languages defined in {@link Bajo.TConfig|`config.intl.supported`}
573
+ * and save them in `this[ns].intl` object.
473
574
  *
474
575
  * @method
475
- * @param {string} ns - Plugin name (namespace)
576
+ * @param {string} ns - Plugin namespace
476
577
  */
477
578
  loadIntl = (ns) => {
478
579
  const { fs } = this.lib
@@ -536,28 +637,34 @@ class App {
536
637
  }
537
638
 
538
639
  /**
539
- * Translate text and interpolate with given `params`.
640
+ * Translate text to the current language.
641
+ *
642
+ * It will search for the translation in the plugin's `intl` object first, interpolating the parameters if found,
643
+ * then fallback to `bajo` plugin's `intl` object if not found.
540
644
  *
541
645
  * If the last parameter is an object with `lang` key, it will use that language instead of the default one.
542
646
  *
543
647
  * There is a shortcut to this method attached on all plugins. You'll normally call that shorcut
544
- * instead of this method, because it is bound to plugin's namespace already
648
+ * instead of this method, because that shortcut is bound to plugin's namespace already
545
649
  *
546
- * @method
547
- * @param {string} ns - Namespace
548
- * @param {string} text - Text to translate
549
- * @param {...any} params - Arguments
550
- * @returns {string}
551
- * @example
650
+ * ```js
552
651
  * ... within your main plugin
553
652
  * const translated = this.app.t('main', 'My cute cat is %s', 'purring')
554
653
  * // or within your plugin
555
654
  * const translated = this.t('My cute cat is %s', 'purring')
655
+ * ```
656
+ *
657
+ * @method
658
+ * @param {string} ns - Namespace
659
+ * @param {string} text - Text to translate
660
+ * @param {...*} params - Parameters to interpolate into the translation text
661
+ * @returns {string}
556
662
  */
557
663
  t = (ns, text, ...params) => {
558
664
  const { formatText, isSet } = this.lib.aneka
559
- const { isArray, last } = this.lib._
665
+ const { isArray, last, get } = this.lib._
560
666
  const { join } = this.bajo
667
+ if (!get(this, 'bajo.config.intl')) return formatText(text, ...params)
561
668
  let { text: newText, trans, params: args } = this._prepTrans(ns, text, params)
562
669
  if (!isSet(trans)) trans = newText
563
670
  const lang = isPlainObject(last(args)) ? last(args).lang : undefined
@@ -582,7 +689,8 @@ class App {
582
689
  }
583
690
 
584
691
  /**
585
- * Helper method to list all supported config formats.
692
+ * Method to list all supported config formats, that is, all file extensions that are supported
693
+ * by the app's config handlers.
586
694
  *
587
695
  * @method
588
696
  * @param {boolean} [noDot] - If `true`, it will return the list without dot prefix
@@ -594,7 +702,7 @@ class App {
594
702
  }
595
703
 
596
704
  /**
597
- * Start a plugin.
705
+ * Start a plugin by its namespace. It will call the plugin's `start` method with the provided arguments.
598
706
  *
599
707
  * @method
600
708
  * @param {string} ns - Plugin namespace.
@@ -605,8 +713,13 @@ class App {
605
713
  }
606
714
 
607
715
  /**
608
- * Stop a plugin.
716
+ * Stop a plugin by its namespace. It will call the plugin's `stop` method with the provided arguments.
717
+ *
718
+ * Reserved for future use.
609
719
  *
720
+ * > **Note**: Basically it is not a good idea to stop a plugin because other plugins
721
+ * might be dependent on it. But if we could find a way to safely stop a plugin without breaking things,
722
+ * this will be a cool feature because it allows dynamic management of plugins without restarting the application.
610
723
  * @method
611
724
  * @param {string} ns - Plugin namespace.
612
725
  * @param {...any} args - Arguments to pass to the plugin's stop method