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
@@ -0,0 +1,264 @@
1
+ import ora from 'ora'
2
+ import lodash from 'lodash'
3
+ import aneka from 'aneka'
4
+ const { defaultsDeep } = aneka
5
+
6
+ const { isPlainObject } = lodash
7
+
8
+ /**
9
+ * @typedef TPrintOptions
10
+ * @property {boolean} [showDatetime=false] - Show actual date & time
11
+ * @property {boolean} [showCounter=false] - Show as counter
12
+ * @property {boolean} [silent] - Suppress any messages. Defaults to the one set in {@tutorial config}
13
+ * @property {Object} [ora] - {@link https://github.com/sindresorhus/ora#api|Ora's options} object
14
+ * @see {@link Print}
15
+ */
16
+
17
+ /**
18
+ * Universal print engine, supports text translation using {@link App#t|app's built-in translation}.
19
+ *
20
+ * Features many methods to display things on screen/console using {@link https://github.com/sindresorhus/ora|ora}
21
+ * based spinner.
22
+ *
23
+ * @class
24
+ */
25
+ class Print {
26
+ /**
27
+ * @param {Plugin} plugin - Plugin instance
28
+ * @param {TPrintOptions} [options={}] - Options object
29
+ */
30
+ constructor (plugin, options = {}) {
31
+ /**
32
+ * Options object
33
+ * @type {TPrintOptions}
34
+ */
35
+ this.options = options
36
+
37
+ /**
38
+ * Attached plugin
39
+ * @type {Plugin}
40
+ */
41
+ this.plugin = plugin
42
+
43
+ /**
44
+ * The app instance
45
+ * @type {App}
46
+ */
47
+ this.app = plugin.app
48
+ if (this.app.applet) {
49
+ if (this.app.bajo.config.counter) this.options.showCounter = true
50
+ if (this.app.bajo.config.datetime) this.options.showDatetime = true
51
+ }
52
+
53
+ /**
54
+ * Time when instance is created
55
+ * @type {Object}
56
+ * @see {@link https://day.js.org|dayjs}  object
57
+ */
58
+ this.startTime = this.app.lib.dayjs()
59
+
60
+ /**
61
+ * ora instance
62
+ * @see {@link https://github.com/sindresorhus/ora|ora}
63
+ */
64
+ this.ora = ora(this.options.ora)
65
+ this.setOpts()
66
+ }
67
+
68
+ /**
69
+ * Setting spinner options; override the one passed at constructor
70
+ *
71
+ * @method
72
+ * @param {any[]} [args=[]] - Array of options. If the last argument is an object, it will be used to override ora options
73
+ */
74
+ setOpts = (args = []) => {
75
+ const { silent } = this.app.bajo.config
76
+ let opts = {}
77
+ if (isPlainObject(args.slice(-1)[0])) opts = args.pop()
78
+ this.options.silent = !!(silent || this.options.silent)
79
+ this.options = defaultsDeep(opts, this.options)
80
+ }
81
+
82
+ /**
83
+ * Translate, prefixed with counter and/or datetime etc
84
+ *
85
+ * @param {string} text - Text to use
86
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora's options
87
+ * @returns {string}
88
+ */
89
+ buildText = (text, ...args) => {
90
+ text = this.plugin.t(text, ...args)
91
+ this.setOpts(args)
92
+ const prefixes = []
93
+ if (this.options.showDatetime) prefixes.push('[' + this.app.lib.dayjs().toISOString() + ']')
94
+ if (this.options.showCounter) prefixes.push('[' + this.getElapsed() + ']')
95
+ // if (prefixes.length > 0) this.ora.prefixText = this.ora.prefixText + prefixes.join(' ')
96
+ if (prefixes.length > 0) text = prefixes.join(' ') + ' ' + text
97
+ return text
98
+ }
99
+
100
+ /**
101
+ * Set spinner's text
102
+ *
103
+ * @method
104
+ * @param {string} text - Text to use
105
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora's options
106
+ * @returns {Print} Return the instance itself, usefull for method chaining
107
+ */
108
+ setText = (text, ...args) => {
109
+ text = this.buildText(text, ...args)
110
+ this.ora.text = text
111
+ return this
112
+ }
113
+
114
+ /**
115
+ * Get elapsed time since instance is created
116
+ *
117
+ * @method
118
+ * @param {string} [unit=hms] - Unit's time. Put 'hms' (default) to get hour, minute, second format or of any format supported by {@link https://day.js.org/docs/en/display/difference|dayjs}
119
+ * @returns {string} Elapsed time since start
120
+ * @see {@link https://day.js.org/docs/en/display/difference|dayjs duration format}
121
+ */
122
+ getElapsed = (unit = 'hms') => {
123
+ const u = unit === 'hms' ? 'second' : unit
124
+ const elapsed = this.app.lib.dayjs().diff(this.startTime, u)
125
+ return unit === 'hms' ? this.app.lib.aneka.secToHms(elapsed) : elapsed
126
+ }
127
+
128
+ /**
129
+ * Start the spinner
130
+ *
131
+ * @method
132
+ * @param {string} text - Text to use
133
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora's options
134
+ * @returns {Print} Return the instance itself, usefull for method chaining
135
+ */
136
+ start = (text, ...args) => {
137
+ this.setOpts(args)
138
+ this.setText(text, ...args)
139
+ this.ora.start()
140
+ return this
141
+ }
142
+
143
+ /**
144
+ * Stop the spinner
145
+ *
146
+ * @method
147
+ * @returns {Print} Return the instance itself, usefull for method chaining
148
+ */
149
+ stop = () => {
150
+ this.ora.stop()
151
+ return this
152
+ }
153
+
154
+ /**
155
+ * Print success message, prefixed with a check icon
156
+ *
157
+ * @method
158
+ * @param {string} text - Text to use
159
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
160
+ * @returns {Print} Return the instance itself, usefull for method chaining
161
+ */
162
+ succeed = (text, ...args) => {
163
+ this.setText(text, ...args)
164
+ this.ora.succeed()
165
+ return this
166
+ }
167
+
168
+ /**
169
+ * Print failed message, prefixed with a cross icon
170
+ *
171
+ * @method
172
+ * @param {string} text - Text to use
173
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
174
+ * @returns {Print} Return the instance itself, usefull for method chaining
175
+ */
176
+ fail = (text, ...args) => {
177
+ this.setText(text, ...args)
178
+ this.ora.fail()
179
+ return this
180
+ }
181
+
182
+ /**
183
+ * Print warning message, prefixed with a warn icon
184
+ *
185
+ * @method
186
+ * @param {string} text - Text to use
187
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
188
+ * @returns {Print} Return the instance itself, usefull for method chaining
189
+ */
190
+ warn = (text, ...args) => {
191
+ this.setText(text, ...args)
192
+ this.ora.warn()
193
+ return this
194
+ }
195
+
196
+ /**
197
+ * Print information message, prefixed with an info icon
198
+ *
199
+ * @method
200
+ * @param {string} text - Text to use
201
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
202
+ * @returns {Print} Return the instance itself, usefull for method chaining
203
+ */
204
+ info = (text, ...args) => {
205
+ this.setText(text, ...args)
206
+ this.ora.info()
207
+ return this
208
+ }
209
+
210
+ /**
211
+ * Clear spinner text
212
+ *
213
+ * @method
214
+ * @returns {Print} Return the instance itself, usefull for method chaining
215
+ */
216
+ clear = () => {
217
+ this.ora.clear()
218
+ return this
219
+ }
220
+
221
+ /**
222
+ * Force render spinner
223
+ *
224
+ * @method
225
+ * @returns {Print} Return the instance itself, usefull for method chaining
226
+ */
227
+ render = () => {
228
+ this.ora.render()
229
+ return this
230
+ }
231
+
232
+ /**
233
+ * Print failed message, prefixed with a cross icon and exit
234
+ *
235
+ * @method
236
+ * @param {string} text - Text to use
237
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
238
+ */
239
+ fatal = (text, ...args) => {
240
+ if (text instanceof Error) {
241
+ text = text.message
242
+ args = []
243
+ }
244
+ this.setText(text, ...args)
245
+ this.ora.fail()
246
+ if (text instanceof Error && this.app.bajo.config.log.level === 'trace') console.error(text)
247
+ this.app.exit()
248
+ }
249
+
250
+ /**
251
+ * Create a new print instance
252
+ *
253
+ * @method
254
+ * @param {TPrintOptions} [options] - Options object. If not provided, defaults to the current options
255
+ * @returns {Print} Return new print instance
256
+ */
257
+ spinner = (options) => {
258
+ const spin = new Print(this.plugin, options ?? this.options)
259
+ spin.startTime = this.startTime.clone()
260
+ return spin
261
+ }
262
+ }
263
+
264
+ export default Print
package/class/plugin.js CHANGED
@@ -1,117 +1,169 @@
1
- import BasePlugin from './base/plugin.js'
2
1
  import lodash from 'lodash'
3
- import omittedPluginKeys from '../lib/omitted-plugin-keys.js'
4
- import readAllConfigs from '../lib/read-all-configs.js'
2
+ import Err from './misc/err.js'
5
3
 
6
- const { pick, omit } = lodash
4
+ const { get, isEmpty, cloneDeep, omit, isPlainObject, camelCase } = lodash
7
5
 
8
6
  /**
9
- * This is the class that your own plugin suppose to extend. Don't use {@link BasePlugin}
10
- * unless you know what you're doing.
7
+ * This is the **mother** of all plugin classes. All Bajo plugin classess inherit from this class
8
+ * respectfully.
9
+ *
10
+ * There are currently only two main plugins available:
11
+ * - {@link Bajo} - Core plugin class, responsible for system wide setup and boot process. You should not touch this obviously
12
+ * - {@link Base} - Base plugin class your own plugin should extend from
11
13
  *
12
14
  * @class
13
15
  */
16
+ class Plugin {
17
+ /**
18
+ * Package name, the one from package.json
19
+ *
20
+ * @memberof Plugin
21
+ * @constant {string}
22
+ */
23
+ static pkgName
24
+
25
+ /**
26
+ * Namespace (ns) or plugin's name. Simply the camel cased version of plugin's package name
27
+ *
28
+ * @memberof Plugin
29
+ * @constant {string}
30
+ */
31
+ static ns
32
+
33
+ /**
34
+ * Plugin alias. Derived plugin must provide its own, unique alias. If it left blank,
35
+ * Bajo will provide this automatically (by using the kebab-cased version of plugin name)
36
+ *
37
+ * @readonly
38
+ * @memberof Plugin
39
+ * @type {string}
40
+ */
41
+ static alias = ''
14
42
 
15
- class Plugin extends BasePlugin {
16
43
  /**
17
44
  * @param {string} pkgName - Package name (the one you use in package.json)
18
45
  * @param {Object} app - App instance reference. Usefull to call app method inside a plugin
19
46
  */
20
47
  constructor (pkgName, app) {
21
- super(pkgName, app)
48
+ this.constructor.pkgName = pkgName
49
+ this.constructor.ns = camelCase(pkgName)
50
+
22
51
  /**
23
- * Plugin alias. Derived plugin must provide its own, unique alias. If it left blank,
24
- * Bajo will provide this automatically
52
+ * Reference to app instance
25
53
  *
26
- * @type {string}
54
+ * @type {Object}
27
55
  */
28
- this.alias = ''
56
+ this.app = app
29
57
 
30
58
  /**
31
- * Dependencies to other plugins. Enter all plugin's package name your plugin dependent from.
59
+ * Config object
32
60
  *
33
- * Semver is also supported.
61
+ * @type {Object}
62
+ * @see {@tutorial config}
63
+ */
64
+ this.config = {}
65
+
66
+ /**
67
+ * Shortcut to {@link App#log} with prefix parameter set to this plugin name.
34
68
  *
35
- * @type {Array}
69
+ * @type {Log}
36
70
  */
37
- this.dependencies = []
38
- this.state = {}
71
+ this.log = {
72
+ trace: (...params) => this.app.log.trace(this.ns, ...params),
73
+ debug: (...params) => this.app.log.debug(this.ns, ...params),
74
+ info: (...params) => this.app.log.info(this.ns, ...params),
75
+ warn: (...params) => this.app.log.warn(this.ns, ...params),
76
+ error: (...params) => this.app.log.error(this.ns, ...params),
77
+ fatal: (...params) => this.app.log.fatal(this.ns, ...params),
78
+ silent: (...params) => this.app.log.silent(this.ns, ...params)
79
+ }
39
80
  }
40
81
 
41
82
  /**
42
- * Load config from file in data directory, program arguments and environment variables. Level of importance:
43
- * ```Env Variables > Program Arguments > Config File```
83
+ * Get plugin's config value
44
84
  *
45
85
  * @method
46
- * @async
86
+ * @param {string} [path] - dot separated config path (think of lodash's 'get'). If not provided, the full config will be given
87
+ * @param {Object} [options={}] - Options
88
+ * @param {any} [options.defValue={}] - Default value to use if returned object is undefined
89
+ * @param {string[]} [options.omit=[]] - Omit these keys from returned object
90
+ * @param {boolean} [options.noClone=false] - Set true to NOT clone returned object
91
+ * @returns {Object} Returned object. If no path provided, the whole config object is returned
47
92
  */
48
- loadConfig = async () => {
49
- const { defaultsDeep } = this.lib.aneka
50
- const { get } = this.lib._
51
- const { log, readJson, parseObject, getModuleDir } = this.app.bajo
52
- log.trace('- %s', this.name)
53
- const dir = this.name === this.app.bajo.mainNs ? (`${this.app.bajo.dir.base}/${this.app.bajo.mainNs}`) : getModuleDir(this.pkgName)
54
- let cfg = await readAllConfigs.call(this.app, `${dir}/config`)
55
- this.alias = this.alias ?? (this.pkgName.slice(0, 5) === 'bajo-' ? this.pkgName.slice(5).toLowerCase() : this.name.toLowerCase())
56
- this.alias = this.alias.toLowerCase()
57
-
58
- this.dir = {
59
- pkg: dir,
60
- data: `${this.app.bajo.dir.data}/plugins/${this.name}`
61
- }
62
- const file = `${dir + (this.name === this.app.bajo.mainNs ? '/..' : '')}/package.json`
63
- const pkgJson = await readJson(file)
64
- this.pkg = pick(pkgJson,
65
- ['name', 'version', 'description', 'author', 'license', 'homepage'])
66
- if (this.name === this.app.bajo.mainNs) {
67
- this.alias = this.app.bajo.mainNs
68
- this.title = this.title ?? this.alias
69
- }
70
- // merge with config from datadir
71
- try {
72
- const altCfg = await readAllConfigs.call(this.app, `${this.app.bajo.dir.data}/config/${this.name}`)
73
- cfg = defaultsDeep({}, omit(altCfg, omittedPluginKeys), cfg)
74
- } catch (err) {}
75
- const cfgEnv = omit(get(this, `app.env._.${this.name}`, {}), omittedPluginKeys) ?? {}
76
- const cfgArgv = omit(get(this, `app.argv._.${this.name}`, {}), omittedPluginKeys) ?? {}
77
- const envArgv = defaultsDeep({}, cfgEnv, cfgArgv)
78
- cfg = defaultsDeep({}, envArgv ?? {}, cfg ?? {}, this.config ?? {})
79
- this.title = this.title ?? cfg.title ?? this.alias
80
-
81
- this.dependencies = this.dependencies ?? []
82
- this.config = parseObject(cfg, { parseValue: true })
93
+ getConfig = (path, options = {}) => {
94
+ let obj = isEmpty(path) ? this.config : get(this.config, path, options.defValue ?? {})
95
+ options.omit = options.omit ?? []
96
+ if (isPlainObject(obj) && !isEmpty(options.omit)) obj = omit(obj, options.omit)
97
+ if (!options.noClone) obj = cloneDeep(obj)
98
+ return obj
83
99
  }
84
100
 
85
101
  /**
86
- * After config is read, plugin will be initialized. You can still change your config here,
87
- * because after plugin is initialized, config will be deep frozen.
102
+ * Create an instance of {@link Err} object
88
103
  *
89
104
  * @method
90
- * @async
105
+ * @param {msg} msg - Error message
106
+ * @param {...any} [args] - Argument variables you might want to add to the error object
107
+ * @returns {Object} Err instance
91
108
  */
92
- init = async () => {
109
+ error = (msg, ...args) => {
110
+ if (!this.print) return new Error(msg, ...args)
111
+ const error = new Err(this, msg, ...args)
112
+ return error.write()
93
113
  }
94
114
 
95
115
  /**
96
- * This method will be called after plugin's init
116
+ * Create an instance of Err object, display it on screen and then force
117
+ * terminate the app process
97
118
  *
98
119
  * @method
99
- * @async
120
+ * @param {msg} msg - Error message
121
+ * @param {...any} [args] - Argument variables you might want to add to the error object
100
122
  */
101
- start = async () => {
123
+ fatal = (msg, ...args) => {
124
+ if (!this.print) return new Error(msg, ...args)
125
+ const error = new Err(this, msg, ...args)
126
+ error.fatal()
102
127
  }
103
128
 
104
- stop = async () => {
129
+ /**
130
+ * Getter for plugin's package name
131
+ *
132
+ * @type {string}
133
+ */
134
+ get pkgName () {
135
+ return this.constructor.pkgName
105
136
  }
106
137
 
107
138
  /**
108
- * Upon app termination, this method will be called first. Mostly useful for system cleanup,
109
- * delete temporary files, freeing resources etc.
139
+ * Getter for plugin's ns
110
140
  *
111
- * @method
112
- * @async
141
+ * @type {string}
142
+ */
143
+ get ns () {
144
+ return this.constructor.ns
145
+ }
146
+
147
+ /**
148
+ * Getter for plugin's alias
149
+ *
150
+ * @type {string}
151
+ */
152
+ get alias () {
153
+ return this.constructor.alias
154
+ }
155
+
156
+ /**
157
+ * Translate text and interpolate with given ```args```.
158
+ *
159
+ * Shortcut to {@link App#t} with ns parameter set to this plugin namespace.
160
+ *
161
+ * @param {string} text - Text to translate
162
+ * @param {...any} params - Variables to interpolate to ```text```
163
+ * @returns {string}
113
164
  */
114
- exit = async () => {
165
+ t = (text, ...params) => {
166
+ return this.app.t(this.ns, text, ...params)
115
167
  }
116
168
  }
117
169