bajo 1.2.8 → 2.0.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 (167) hide show
  1. package/.jsdoc.conf.json +6 -3
  2. package/README.md +8 -148
  3. package/class/app.js +113 -0
  4. package/{boot/class/bajo-core.js → class/bajo.js} +449 -37
  5. package/{boot/class/error.js → class/base/err.js} +29 -3
  6. package/class/base/log.js +205 -0
  7. package/class/base/plugin.js +177 -0
  8. package/class/base/print.js +272 -0
  9. package/class/helper/bajo.js +344 -0
  10. package/class/helper/plugin.js +169 -0
  11. package/{boot/class/bajo-plugin.js → class/plugin.js} +60 -3
  12. package/docs/App.html +3 -0
  13. package/docs/Bajo.html +15 -0
  14. package/docs/BasePlugin.html +5 -0
  15. package/docs/Err.html +3 -0
  16. package/docs/Log.html +3 -0
  17. package/docs/Plugin.html +3 -0
  18. package/docs/Print.html +3 -0
  19. package/docs/bitcoin.jpeg +0 -0
  20. package/docs/class_app.js.html +116 -0
  21. package/docs/class_bajo.js.html +1100 -0
  22. package/docs/class_base_err.js.html +99 -0
  23. package/docs/class_base_log.js.html +208 -0
  24. package/docs/class_base_plugin.js.html +180 -0
  25. package/docs/class_base_print.js.html +275 -0
  26. package/docs/class_helper_bajo.js.html +347 -0
  27. package/docs/class_helper_plugin.js.html +172 -0
  28. package/docs/class_plugin.js.html +121 -0
  29. package/docs/data/search.json +1 -0
  30. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  31. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  32. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  33. package/docs/global.html +3 -0
  34. package/docs/index.html +3 -0
  35. package/docs/lib_create-method.js.html +42 -0
  36. package/docs/lib_formats.js.html +68 -0
  37. package/docs/lib_log-levels.js.html +28 -0
  38. package/docs/lib_resolve-path.js.html +30 -0
  39. package/docs/lib_shim.js.html +35 -0
  40. package/docs/module-class_helper_bajo.html +3 -0
  41. package/docs/module-class_helper_plugin.html +3 -0
  42. package/docs/module-lib_create-method.html +3 -0
  43. package/docs/module-lib_formats.html +3 -0
  44. package/docs/module-lib_log-levels.html +3 -0
  45. package/docs/module-lib_resolve-path.html +3 -0
  46. package/docs/module-lib_shim.html +3 -0
  47. package/docs/scripts/core.js +726 -0
  48. package/docs/scripts/core.min.js +23 -0
  49. package/docs/scripts/resize.js +90 -0
  50. package/docs/scripts/search.js +265 -0
  51. package/docs/scripts/search.min.js +6 -0
  52. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  53. package/docs/scripts/third-party/fuse.js +9 -0
  54. package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
  55. package/docs/scripts/third-party/hljs-line-num.js +1 -0
  56. package/docs/scripts/third-party/hljs-original.js +5171 -0
  57. package/docs/scripts/third-party/hljs.js +1 -0
  58. package/docs/scripts/third-party/popper.js +5 -0
  59. package/docs/scripts/third-party/tippy.js +1 -0
  60. package/docs/scripts/third-party/tocbot.js +672 -0
  61. package/docs/scripts/third-party/tocbot.min.js +1 -0
  62. package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
  63. package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
  64. package/docs/styles/clean-jsdoc-theme-light.css +482 -0
  65. package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  66. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  67. package/docs/styles/clean-jsdoc-theme.min.css +1 -0
  68. package/docs/tutorial-contribution.html +3 -0
  69. package/docs/tutorial-ecosystem.html +3 -0
  70. package/docs/tutorial-getting-started.html +13 -0
  71. package/docs/tutorial-plugin-dev.html +3 -0
  72. package/docs/tutorial-user-guide.html +3 -0
  73. package/lib/create-method.js +39 -0
  74. package/{boot/lib → lib}/formats.js +28 -0
  75. package/lib/log-levels.js +25 -0
  76. package/{boot/lib → lib}/parse-args-argv.js +1 -1
  77. package/{boot/lib → lib}/resolve-path.js +12 -0
  78. package/{boot/lib → lib}/shim.js +10 -0
  79. package/misc-docs/bitcoin.jpeg +0 -0
  80. package/misc-docs/contribution.md +20 -0
  81. package/{docs → misc-docs}/ecosystem.md +41 -12
  82. package/misc-docs/getting-started.md +142 -0
  83. package/misc-docs/plugin-dev.md +0 -0
  84. package/misc-docs/toc.json +17 -0
  85. package/misc-docs/user-guide.md +1 -0
  86. package/package.json +14 -12
  87. package/bajoBook/book/doc/.metadata.json +0 -28
  88. package/bajoBook/book/doc/How-to-Make-a-Paper-Boat-564x400@2x.jpg +0 -0
  89. package/bajoBook/book/doc/pages/guides/definition.md +0 -7
  90. package/bajoBook/book/doc/pages/guides/intro.md +0 -3
  91. package/bajoBook/book/doc/pages/guides/setup.md +0 -22
  92. package/bajoBook/book/reference/.metadata.json +0 -152
  93. package/bajoBook/book/reference/concept-leadership-business-with-paper-boats.jpg +0 -0
  94. package/bajoBook/book/reference/pages/configuration/configuration-file.md +0 -52
  95. package/bajoBook/book/reference/pages/helper/break-ns-path.md +0 -24
  96. package/bajoBook/book/reference/pages/helper/build-collections.md +0 -19
  97. package/bajoBook/book/reference/pages/helper/call-helper-or-handler.md +0 -35
  98. package/bajoBook/book/reference/pages/helper/current-loc.md +0 -28
  99. package/bajoBook/book/reference/pages/helper/dayjs.md +0 -13
  100. package/bajoBook/book/reference/pages/helper/defaults-deep.md +0 -29
  101. package/bajoBook/book/reference/pages/helper/dump.md +0 -32
  102. package/bajoBook/book/reference/pages/helper/each-plugins.md +0 -24
  103. package/bajoBook/book/reference/pages/helper/envs.md +0 -11
  104. package/bajoBook/book/reference/pages/helper/error.md +0 -29
  105. package/bajoBook/book/reference/pages/helper/fatal.md +0 -18
  106. package/bajoBook/book/reference/pages/helper/freeze.md +0 -13
  107. package/bajoBook/book/reference/pages/helper/generate-id.md +0 -36
  108. package/bajoBook/book/reference/pages/helper/get-config.md +0 -27
  109. package/bajoBook/book/reference/pages/helper/get-global-module-dir.md +0 -13
  110. package/bajoBook/book/reference/pages/helper/get-helper.md +0 -13
  111. package/bajoBook/book/reference/pages/helper/get-item-by-name.md +0 -13
  112. package/bajoBook/book/reference/pages/helper/get-key-by-value.md +0 -13
  113. package/bajoBook/book/reference/pages/helper/get-module-dir.md +0 -13
  114. package/bajoBook/book/reference/pages/helper/get-plugin-data-dir.md +0 -13
  115. package/bajoBook/book/reference/pages/helper/get-plugin-name.md +0 -13
  116. package/bajoBook/book/reference/pages/helper/get-plugin.md +0 -13
  117. package/bajoBook/book/reference/pages/helper/import-module.md +0 -13
  118. package/bajoBook/book/reference/pages/helper/import-pkg.md +0 -13
  119. package/bajoBook/book/reference/pages/helper/is-empty-dir.md +0 -13
  120. package/bajoBook/book/reference/pages/helper/is-log-in-range.md +0 -13
  121. package/bajoBook/book/reference/pages/helper/is-set.md +0 -13
  122. package/bajoBook/book/reference/pages/helper/is-valid-app.md +0 -13
  123. package/bajoBook/book/reference/pages/helper/is-valid-plugin.md +0 -13
  124. package/bajoBook/book/reference/pages/helper/log-levels.md +0 -13
  125. package/bajoBook/book/reference/pages/helper/log.md +0 -13
  126. package/bajoBook/book/reference/pages/helper/paginate.md +0 -13
  127. package/bajoBook/book/reference/pages/helper/pascal-case.md +0 -13
  128. package/bajoBook/book/reference/pages/helper/print.md +0 -13
  129. package/bajoBook/book/reference/pages/helper/read-config.md +0 -13
  130. package/bajoBook/book/reference/pages/helper/read-json.md +0 -13
  131. package/bajoBook/book/reference/pages/helper/resolve-path.md +0 -13
  132. package/bajoBook/book/reference/pages/helper/resolve-tpl-path.md +0 -13
  133. package/bajoBook/book/reference/pages/helper/run-hook.md +0 -13
  134. package/bajoBook/book/reference/pages/helper/save-as-download.md +0 -13
  135. package/bajoBook/book/reference/pages/helper/titleize.md +0 -13
  136. package/bajoBook/book/reference/pages/helper/white-space.md +0 -13
  137. package/boot/class/app.js +0 -67
  138. package/boot/class/bajo-core/boot-order.js +0 -35
  139. package/boot/class/bajo-core/boot-plugins.js +0 -17
  140. package/boot/class/bajo-core/build-config.js +0 -85
  141. package/boot/class/bajo-core/build-plugins.js +0 -44
  142. package/boot/class/bajo-core/collect-config-handlers.js +0 -20
  143. package/boot/class/bajo-core/exit-handler.js +0 -53
  144. package/boot/class/bajo-core/run-as-applet.js +0 -26
  145. package/boot/class/bajo-plugin/attach-method.js +0 -14
  146. package/boot/class/bajo-plugin/build-config.js +0 -15
  147. package/boot/class/bajo-plugin/check-clash.js +0 -18
  148. package/boot/class/bajo-plugin/check-dependency.js +0 -39
  149. package/boot/class/bajo-plugin/collect-hooks.js +0 -31
  150. package/boot/class/bajo-plugin/run.js +0 -23
  151. package/boot/class/log.js +0 -90
  152. package/boot/class/plugin.js +0 -79
  153. package/boot/class/print.js +0 -153
  154. package/boot/lib/create-method.js +0 -33
  155. package/boot/lib/log-levels.js +0 -1
  156. package/test/method/isSet.js +0 -43
  157. /package/{bajo → extend/bajo}/intl/en-US.json +0 -0
  158. /package/{bajo → extend/bajo}/intl/id.json +0 -0
  159. /package/{waibuStatic → extend/waibuStatic}/virtual.json +0 -0
  160. /package/{boot/index.js → index.js} +0 -0
  161. /package/{boot/lib → lib}/current-loc.js +0 -0
  162. /package/{boot/lib → lib}/dayjs.js +0 -0
  163. /package/{boot/lib → lib}/import-module.js +0 -0
  164. /package/{boot/lib → lib}/omitted-plugin-keys.js +0 -0
  165. /package/{boot/lib → lib}/parse-env.js +0 -0
  166. /package/{boot/lib → lib}/read-all-configs.js +0 -0
  167. /package/{docs/hook.md → misc-docs/.hook.md} +0 -0
@@ -0,0 +1,272 @@
1
+ import ora from 'ora'
2
+ import lodash from 'lodash'
3
+ import fs from 'fs-extra'
4
+ import Sprintf from 'sprintf-js'
5
+ const { sprintf } = Sprintf
6
+ let unknownLangWarning = false
7
+
8
+ const { isString, last, isPlainObject, get, without, reverse, map } = lodash
9
+
10
+ /**
11
+ * Print class. Use sprintf to interpolate pattern and variable. Support
12
+ * many methods to display things on screen including {@link https://github.com/sindresorhus/ora|ora} based spinner.
13
+ *
14
+ * It also serve as the foundation of Bajo's I18n lightweight system.
15
+ *
16
+ * @class
17
+ */
18
+ class Print {
19
+ /**
20
+ * Class constructor
21
+ *
22
+ * @param {Object} plugin - Plugin instance
23
+ * @param {Object} [opts={}] - Options to pass to {@link https://github.com/sindresorhus/ora|ora}
24
+ */
25
+ constructor (plugin, opts = {}) {
26
+ this.opts = opts
27
+ this.plugin = plugin
28
+ this.startTime = this.plugin.app.bajo.lib.dayjs()
29
+ this.setOpts()
30
+ this.ora = ora(this.opts)
31
+ this.intl = {}
32
+ }
33
+
34
+ /**
35
+ * Initialize print engine and read plugin's translation files
36
+ *
37
+ * @method
38
+ */
39
+ init = () => {
40
+ for (const l of this.plugin.app.bajo.config.intl.supported) {
41
+ this.intl[l] = {}
42
+ const path = `${this.plugin.dir.pkg}/extend/bajo/intl/${l}.json`
43
+ if (!fs.existsSync(path)) continue
44
+ const trans = fs.readFileSync(path, 'utf8')
45
+ try {
46
+ this.intl[l] = JSON.parse(trans)
47
+ } catch (err) {}
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Interpolate and translate text according to the chosen language
53
+ *
54
+ * @method
55
+ * @param {string} text - Text pattern to translate. See {@link https://github.com/alexei/sprintf.js|sprintf} for all supported token & format
56
+ * @param {...any} [args] - Variables to interpolate with text pattern above. If the last argument is an object, it will be use to override default translation option. Example: to force language to 'id', pass the last argument as "{ lang: 'id' }"
57
+ * @returns {string} Interpolated & translated text
58
+ */
59
+ write = (text, ...args) => {
60
+ const opts = last(args)
61
+ let lang = this.plugin.app.bajo.config.lang
62
+ if (isPlainObject(opts)) {
63
+ args.pop()
64
+ if (opts.lang) lang = opts.lang
65
+ }
66
+ const { fallback, supported } = this.plugin.app.bajo.config.intl
67
+ if (!unknownLangWarning && !supported.includes(lang)) {
68
+ unknownLangWarning = true
69
+ this.plugin.app.bajo.log.warn('unsupportedLangFallbackTo%s', fallback)
70
+ }
71
+ const plugins = reverse(without([...this.plugin.app.bajo.pluginNames], this.plugin.name))
72
+ plugins.unshift(this.plugin.name)
73
+ plugins.push('bajo')
74
+
75
+ let trans
76
+ for (const p of plugins) {
77
+ const root = get(this, `plugin.app.${p}.print.intl.${lang}`, {})
78
+ trans = get(root, text)
79
+ if (trans) break
80
+ }
81
+ if (!trans) {
82
+ for (const p of plugins) {
83
+ const root = get(this, `plugin.app.${p}.print.intl.${fallback}`, {})
84
+ trans = get(root, text)
85
+ if (trans) break
86
+ }
87
+ }
88
+ if (!trans) trans = text
89
+ const params = map(args, a => {
90
+ if (!isString(a)) return a
91
+ return a
92
+ })
93
+ return sprintf(trans, ...params)
94
+ }
95
+
96
+ /**
97
+ * Set spinner options
98
+ *
99
+ * @method
100
+ * @param {any[]} [args=[]] - Array of options. If the last argument is an object, it will be used to override ora options
101
+ */
102
+ setOpts = (args = []) => {
103
+ const config = this.plugin.app.bajo.config
104
+ let opts = {}
105
+ if (isPlainObject(args.slice(-1)[0])) opts = args.pop()
106
+ this.opts.isSilent = !!(config.silent || this.opts.isSilent)
107
+ this.opts = this.plugin.lib.aneka.defaultsDeep(opts, this.opts)
108
+ }
109
+
110
+ /**
111
+ * Set spinner text
112
+ *
113
+ * @method
114
+ * @param {string} text - Text to use
115
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
116
+ * @returns {Object} Return itself, usefull to chain methods
117
+ */
118
+ setText = (text, ...args) => {
119
+ text = this.write(text, ...args)
120
+ this.setOpts(args)
121
+ const prefixes = []
122
+ const texts = []
123
+ if (this.opts.showDatetime) prefixes.push('[' + this.plugin.app.bajo.lib.dayjs().toISOString() + ']')
124
+ if (this.opts.showCounter) texts.push('[' + this.getElapsed() + ']')
125
+ if (prefixes.length > 0) this.ora.prefixText = this.ora.prefixText + prefixes.join(' ')
126
+ if (texts.length > 0) text = texts.join(' ') + ' ' + text
127
+ this.ora.text = text
128
+ return this
129
+ }
130
+
131
+ /**
132
+ * Get elapsed time since print instance is created
133
+ *
134
+ * @method
135
+ * @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}
136
+ * @returns {string} Elapsed time since start
137
+ */
138
+ getElapsed = (unit = 'hms') => {
139
+ const u = unit === 'hms' ? 'second' : unit
140
+ const elapsed = this.plugin.lib.dayjs().diff(this.startTime, u)
141
+ return unit === 'hms' ? this.plugin.lib.aneka.secToHms(elapsed) : elapsed
142
+ }
143
+
144
+ /**
145
+ * Start the spinner
146
+ *
147
+ * @method
148
+ * @param {string} text - Text to use
149
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
150
+ * @returns {Object} Return itself, usefull to chain methods
151
+ */
152
+ start = (text, ...args) => {
153
+ this.setOpts(args)
154
+ this.setText(text, ...args)
155
+ this.ora.start()
156
+ return this
157
+ }
158
+
159
+ /**
160
+ * Stop the spinner
161
+ *
162
+ * @method
163
+ * @returns {Object} Return itself, usefull to chain methods
164
+ */
165
+ stop = () => {
166
+ this.ora.stop()
167
+ return this
168
+ }
169
+
170
+ /**
171
+ * Print success message, prefixed with a check icon
172
+ *
173
+ * @method
174
+ * @param {string} text - Text to use
175
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
176
+ * @returns {Object} Return itself, usefull to chain methods
177
+ */
178
+ succeed = (text, ...args) => {
179
+ this.setText(text, ...args)
180
+ this.ora.succeed()
181
+ return this
182
+ }
183
+
184
+ /**
185
+ * Print failed message, prefixed with a cross icon
186
+ *
187
+ * @method
188
+ * @param {string} text - Text to use
189
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
190
+ * @returns {Object} Return itself, usefull to chain methods
191
+ */
192
+ fail = (text, ...args) => {
193
+ this.setText(text, ...args)
194
+ this.ora.fail()
195
+ return this
196
+ }
197
+
198
+ /**
199
+ * Print warning message, prefixed with a warn icon
200
+ *
201
+ * @method
202
+ * @param {string} text - Text to use
203
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
204
+ * @returns {Object} Return itself, usefull to chain methods
205
+ */
206
+ warn = (text, ...args) => {
207
+ this.setText(text, ...args)
208
+ this.ora.warn()
209
+ return this
210
+ }
211
+
212
+ /**
213
+ * Print failed message, prefixed with an info icon
214
+ *
215
+ * @method
216
+ * @param {string} text - Text to use
217
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
218
+ * @returns {Object} Return itself, usefull to chain methods
219
+ */
220
+ info = (text, ...args) => {
221
+ this.setText(text, ...args)
222
+ this.ora.info()
223
+ return this
224
+ }
225
+
226
+ /**
227
+ * Clear spinner text
228
+ *
229
+ * @method
230
+ * @returns {Object} Return itself, usefull to chain methods
231
+ */
232
+ clear = () => {
233
+ this.ora.clear()
234
+ return this
235
+ }
236
+
237
+ /**
238
+ * Force render spinner
239
+ *
240
+ * @method
241
+ * @returns {Object} Return itself, usefull to chain methods
242
+ */
243
+ render = () => {
244
+ this.ora.render()
245
+ return this
246
+ }
247
+
248
+ /**
249
+ * Print failed message, prefixed with a cross icon and terminate the app process
250
+ *
251
+ * @method
252
+ * @param {string} text - Text to use
253
+ * @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
254
+ */
255
+ fatal = (text, ...args) => {
256
+ this.setText(text, ...args)
257
+ this.ora.fail()
258
+ process.kill(process.pid, 'SIGINT')
259
+ }
260
+
261
+ /**
262
+ * Create a new spinner
263
+ *
264
+ * @method
265
+ * @returns {Object} Return new instance
266
+ */
267
+ spinner = () => {
268
+ return new Print(this.plugin)
269
+ }
270
+ }
271
+
272
+ export default Print
@@ -0,0 +1,344 @@
1
+ import readAllConfigs from '../../lib/read-all-configs.js'
2
+ import currentLoc from '../../lib/current-loc.js'
3
+ import resolvePath from '../../lib/resolve-path.js'
4
+ import omitDeep from 'omit-deep'
5
+ import os from 'os'
6
+ import fs from 'fs-extra'
7
+ import lodash from 'lodash'
8
+ import {
9
+ buildConfigs,
10
+ checkDependencies,
11
+ checkNameAliases,
12
+ attachMethods,
13
+ collectHooks,
14
+ run
15
+ } from './plugin.js'
16
+
17
+ const {
18
+ reduce,
19
+ isNaN,
20
+ forOwn,
21
+ orderBy,
22
+ isFunction,
23
+ isPlainObject,
24
+ map,
25
+ pick,
26
+ values,
27
+ keys,
28
+ set,
29
+ get,
30
+ isString,
31
+ filter,
32
+ trim,
33
+ without,
34
+ uniq,
35
+ camelCase,
36
+ isEmpty,
37
+ omit
38
+ } = lodash
39
+
40
+ const omitted = ['spawn', 'cwd', 'name', 'alias', 'applet', 'a', 'plugins']
41
+
42
+ const defConfig = {
43
+ log: {
44
+ dateFormat: 'YYYY-MM-DDTHH:MM:ss.SSS[Z]',
45
+ plain: false,
46
+ applet: false,
47
+ traceHook: false
48
+ },
49
+ lang: Intl.DateTimeFormat().resolvedOptions().lang ?? 'en-US',
50
+ intl: {
51
+ supported: ['en-US', 'id'],
52
+ fallback: 'en-US',
53
+ lookupOrder: [],
54
+ format: {
55
+ emptyValue: '',
56
+ datetime: { dateStyle: 'medium', timeStyle: 'short' },
57
+ date: { dateStyle: 'medium' },
58
+ time: { timeStyle: 'short' },
59
+ float: { maximumFractionDigits: 2 },
60
+ double: { maximumFractionDigits: 5 },
61
+ smallint: {},
62
+ integer: {}
63
+ },
64
+ unitSys: {
65
+ 'en-US': 'imperial',
66
+ id: 'metric'
67
+ }
68
+ },
69
+ exitHandler: true
70
+ }
71
+
72
+ /**
73
+ * @module
74
+ */
75
+
76
+ /**
77
+ * Building bajo base config. Mostly dealing with directory setups:
78
+ * - determine base directory
79
+ * - check whether data directory is valid
80
+ * - ensure data config directory is there
81
+ *
82
+ * @async
83
+ */
84
+ export async function buildBaseConfig () {
85
+ const { defaultsDeep } = this.lib.aneka
86
+ this.applet = this.app.argv._.applet
87
+ this.config = defaultsDeep({}, this.app.env._, this.app.argv._)
88
+ this.alias = this.name
89
+ set(this, 'dir.base', this.app.dir)
90
+ const path = currentLoc(import.meta).dir + '/../../..'
91
+ set(this, 'dir.pkg', this.resolvePath(path))
92
+ if (!get(this, 'dir.data')) set(this, 'dir.data', `${this.dir.base}/data`)
93
+ this.dir.data = this.resolvePath(this.dir.data)
94
+ if (!fs.existsSync(this.dir.data)) {
95
+ console.log('Data directory (%s) doesn\'t exist yet', this.dir.data)
96
+ process.exit(1)
97
+ }
98
+ fs.ensureDirSync(`${this.dir.data}/config`)
99
+ if (!this.dir.tmp) {
100
+ this.dir.tmp = `${this.resolvePath(os.tmpdir())}/${this.name}`
101
+ fs.ensureDirSync(this.dir.tmp)
102
+ }
103
+ this.app.addPlugin(this)
104
+ }
105
+
106
+ /**
107
+ * Building all plugins:
108
+ * - read the list of plugins from ```.plugins``` file
109
+ * - iterate through the list and build related plugins
110
+ * - attach these plugins to the app instance
111
+ *
112
+ * @async
113
+ */
114
+ export async function buildPlugins () {
115
+ let pluginPkgs = this.config.plugins ?? []
116
+ if (isString(pluginPkgs)) pluginPkgs = [pluginPkgs]
117
+ const pluginsFile = `${this.dir.data}/config/.plugins`
118
+ if (fs.existsSync(pluginsFile)) {
119
+ pluginPkgs = pluginPkgs.concat(filter(map(trim(fs.readFileSync(pluginsFile, 'utf8')).split('\n'), p => trim(p)), b => !isEmpty(b)))
120
+ }
121
+ this.pluginPkgs = map(filter(without(uniq(pluginPkgs), this.mainNs), p => {
122
+ return p[0] !== '#'
123
+ }), p => {
124
+ return trim(p.split('#')[0])
125
+ })
126
+ this.pluginPkgs.push(this.mainNs)
127
+ for (const pkg of this.pluginPkgs) {
128
+ const ns = camelCase(pkg)
129
+ let dir
130
+ if (ns === 'main') {
131
+ dir = `${this.dir.base}/${this.mainNs}`
132
+ fs.ensureDirSync(dir)
133
+ fs.ensureDirSync(`${dir}/plugin`)
134
+ } else dir = this.getModuleDir(pkg)
135
+ let plugin
136
+ const factory = `${dir}/index.js`
137
+ if (fs.existsSync(factory)) {
138
+ const { default: builder } = await import(resolvePath(factory, true))
139
+ const FactoryClass = await builder.call(this, pkg)
140
+ plugin = new FactoryClass()
141
+ if (!(plugin instanceof this.lib.Plugin)) throw new Error(`Plugin package '${pkg}' should be an instance of BajoPlugin`)
142
+ } else {
143
+ plugin = new this.lib.Plugin(pkg, this.app)
144
+ }
145
+ this.pluginNames.push(plugin.name)
146
+ this.app.addPlugin(plugin)
147
+ }
148
+ this.config = omit(this.config, this.pluginNames)
149
+ }
150
+
151
+ /**
152
+ * Collect all config handlers, including the one provided by plugins
153
+ *
154
+ * @async
155
+ */
156
+ export async function collectConfigHandlers () {
157
+ for (const pkg of this.pluginPkgs) {
158
+ let dir
159
+ try {
160
+ dir = this.getModuleDir(pkg)
161
+ } catch (err) {}
162
+ if (!dir) continue
163
+ const file = `${dir}/extend/bajo/config-handlers.js`
164
+ let mod = await this.importModule(file)
165
+ if (!mod) continue
166
+ if (isFunction(mod)) mod = await mod.call(this.app[camelCase(pkg)])
167
+ if (isPlainObject(mod)) mod = [mod]
168
+ this.configHandlers = this.configHandlers.concat(mod)
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Bajo extra config:
174
+ * - reading config file
175
+ * - merge config with arguments & environments values
176
+ * - Set environment (```dev``` or ```prod```)
177
+ *
178
+ * @async
179
+ */
180
+ export async function buildExtConfig () {
181
+ // config merging
182
+ const { defaultsDeep } = this.lib.aneka
183
+ let resp = await readAllConfigs.call(this.app, `${this.dir.data}/config/${this.name}`)
184
+ resp = omitDeep(pick(resp, ['log', 'exitHandler', 'env']), omitted)
185
+ this.config = defaultsDeep({}, this.config, resp, defConfig)
186
+ this.config.env = (this.config.env ?? 'dev').toLowerCase()
187
+ if (values(this.envs).includes(this.config.env)) this.config.env = this.lib.aneka.getKeyByValue(this.envs, this.config.env)
188
+ if (!keys(this.envs).includes(this.config.env)) throw new Error(`Unknown environment '${this.config.env}'. Supported: ${this.join(keys(this.envs))}`)
189
+ process.env.NODE_ENV = this.envs[this.config.env]
190
+ if (!this.config.log.level) this.config.log.level = this.config.env === 'dev' ? 'debug' : 'info'
191
+ if (this.config.silent) this.config.log.level = 'silent'
192
+ if (this.applet) {
193
+ if (!this.pluginPkgs.includes('bajo-cli')) throw new Error('Applet needs to have \'bajo-cli\' loaded first')
194
+ if (!this.config.log.applet) this.config.log.level = 'silent'
195
+ this.config.exitHandler = false
196
+ }
197
+ const exts = map(this.configHandlers, 'ext')
198
+ this.initPrint()
199
+ this.initLog()
200
+ this.log.debug('configHandlers%s', this.join(exts))
201
+ this.config = this.parseObject(this.config, { parseValue: true })
202
+ }
203
+
204
+ /**
205
+ * Setup plugins boot orders by reading plugin's ```.bootorder``` file if provided.
206
+ *
207
+ * @async
208
+ */
209
+ export async function bootOrder () {
210
+ this.log.debug('setupBootOrder')
211
+ const order = reduce(this.pluginPkgs, (o, k, i) => {
212
+ const key = map(k.split(':'), m => trim(m))
213
+ if (key[1] && !isNaN(Number(key[1]))) o[key[0]] = Number(key[1])
214
+ else o[key[0]] = 10000 + i
215
+ return o
216
+ }, {})
217
+ const norder = {}
218
+ for (let n of this.pluginPkgs) {
219
+ n = map(n.split(':'), m => trim(m))[0]
220
+ const dir = n === this.mainNs ? (`${this.dir.base}/${this.mainNs}`) : this.getModuleDir(n)
221
+ if (n !== this.mainNs && !fs.existsSync(dir)) throw this.error('packageNotFoundOrNotBajo%s', n)
222
+ norder[n] = NaN
223
+ try {
224
+ norder[n] = Number(trim(await fs.readFile(`${dir}/.bootorder`, 'utf8')))
225
+ } catch (err) {}
226
+ }
227
+ const result = []
228
+ forOwn(order, (v, k) => {
229
+ const item = { k, v: isNaN(norder[k]) ? v : norder[k] }
230
+ result.push(item)
231
+ })
232
+ this.pluginPkgs = map(orderBy(result, ['v']), 'k')
233
+ this.log.info('runInEnv%s', this.print.write(this.envs[this.config.env]))
234
+ // misc
235
+ this.freeze(this.config)
236
+ }
237
+
238
+ /**
239
+ * Iterate through all plugins loaded and do:
240
+ * 1. {@link module:class/helper/bajo-plugin.buildConfigs|build configs}
241
+ * 2. {@link module:class/helper/bajo-plugin.checkNameAliases|ensure names & aliases uniqueness}
242
+ * 3. {@link module:class/helper/bajo-plugin.checkDependencies|ensure dependencies are met}
243
+ * 4. {@link module:class/helper/bajo-plugin.attachMethods|build and attach dynamic methods}
244
+ * 5. {@link module:class/helper/bajo-plugin.collectHooks|collect hooks}
245
+ * 6. {@link module:class/helper/bajo-plugin.run|run plugins}
246
+ *
247
+ * @async
248
+ */
249
+ export async function bootPlugins () {
250
+ await buildConfigs.call(this.app)
251
+ await checkNameAliases.call(this.app)
252
+ await checkDependencies.call(this.app)
253
+ await attachMethods.call(this.app)
254
+ await collectHooks.call(this.app)
255
+ await run.call(this.app)
256
+ }
257
+
258
+ /**
259
+ * Attach plugins exit handlers and make sure the app shutdowns gracefully
260
+ *
261
+ * @async
262
+ */
263
+ export async function exitHandler () {
264
+ if (!this.config.exitHandler) return
265
+
266
+ async function exit (signal) {
267
+ const { eachPlugins } = this
268
+ this.log.warn('signalReceived%s', signal)
269
+ await eachPlugins(async function () {
270
+ try {
271
+ await this.stop()
272
+ } catch (err) {}
273
+ this.log.trace('exited')
274
+ })
275
+ this.log.debug('appShutdown')
276
+ process.exit(0)
277
+ }
278
+
279
+ process.on('SIGINT', async () => {
280
+ await exit.call(this, 'SIGINT')
281
+ })
282
+
283
+ process.on('SIGTERM', async () => {
284
+ await exit.call(this, 'SIGTERM')
285
+ })
286
+
287
+ process.on('uncaughtException', (error, origin) => {
288
+ setTimeout(() => {
289
+ console.error(error)
290
+ // process.exit(1)
291
+ }, 50)
292
+ })
293
+
294
+ process.on('unhandledRejection', (reason, promise) => {
295
+ const stackFile = reason.stack.split('\n')[1]
296
+ let file
297
+ const info = stackFile.match(/\((.*)\)/) // file is in (<file>)
298
+ if (info) file = info[1]
299
+ else if (stackFile.startsWith(' at ')) file = stackFile.slice(7) // file is stackFile itself
300
+ if (!file) return
301
+ const parts = file.split(':')
302
+ const column = parseInt(parts[parts.length - 1])
303
+ const line = parseInt(parts[parts.length - 2])
304
+ parts.pop()
305
+ parts.pop()
306
+ file = parts.join(':')
307
+ this.log.error({ file, line, column }, '%s', reason.message)
308
+ })
309
+
310
+ process.on('warning', warning => {
311
+ this.log.error('%s', warning.message)
312
+ })
313
+ }
314
+
315
+ /**
316
+ * If app is in ```applet``` mode, this little helper should take care plugin's applet boot process
317
+ *
318
+ * @async
319
+ */
320
+ export async function runAsApplet () {
321
+ const { isString, map, find } = this.lib._
322
+ await this.eachPlugins(async function ({ file }) {
323
+ const { name: ns, alias } = this
324
+ this.app.bajo.applets.push({ ns, file, alias })
325
+ }, { glob: 'applet.js', prefix: 'bajoCli' })
326
+
327
+ this.log.debug('appletModeActivated')
328
+ this.print.info('appRunningAsApplet')
329
+ if (this.applets.length === 0) this.print.fatal('noAppletLoaded')
330
+ let name = this.applet
331
+ if (!isString(this.applet)) {
332
+ const select = await this.importPkg('bajoCli:@inquirer/select')
333
+ name = await select({
334
+ message: this.print.write('Please select:'),
335
+ choices: map(this.applets, t => ({ value: t.ns }))
336
+ })
337
+ }
338
+ const [ns, path] = name.split(':')
339
+ const applet = find(this.applets, a => (a.ns === ns || a.alias === ns))
340
+ if (!applet) this.print.fatal('notFound%s%s', this.print.write('applet'), name)
341
+ await this.runHook(`${this.app[applet.ns]}:beforeAppletRun`)
342
+ await this.app.bajoCli.runApplet(applet, path, ...this.app.args)
343
+ await this.runHook(`${this.app[applet.ns]}:afterAppletRun`)
344
+ }