bajo 2.0.1 → 2.1.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 (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 +378 -54
  7. package/class/bajo.js +228 -149
  8. package/class/base.js +106 -0
  9. package/class/helper/bajo.js +146 -90
  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 +255 -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 +9 -2
  50. package/extend/bajo/intl/id.json +9 -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 +36 -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 -168
  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/base.js ADDED
@@ -0,0 +1,106 @@
1
+ import Plugin from './plugin.js'
2
+
3
+ /**
4
+ * This is the class that your own plugin suppose to extend. Don't use {@link Plugin} directly
5
+ * unless you know what you're doing.
6
+ *
7
+ * @class
8
+ */
9
+
10
+ class Base extends Plugin {
11
+ /**
12
+ * Dependencies to other plugins. Enter all plugin's package name your plugin dependent from.
13
+ *
14
+ * Semver is also supported.
15
+ *
16
+ * @constant {string[]}
17
+ * @memberof Base
18
+ */
19
+ static dependencies = []
20
+
21
+ /**
22
+ * @param {string} pkgName - Package name (the one you use in package.json)
23
+ * @param {Object} app - App instance reference. Usefull to call app method inside a plugin
24
+ */
25
+ constructor (pkgName, app) {
26
+ super(pkgName, app)
27
+ this.state = {}
28
+ }
29
+
30
+ /**
31
+ * Load config from file in data directory, program arguments and environment variables. Level of importance:
32
+ * ```Env Variables > Program Arguments > Config File```
33
+ *
34
+ * @method
35
+ * @async
36
+ */
37
+ loadConfig = async () => {
38
+ const { defaultsDeep } = this.app.lib.aneka
39
+ const { get, kebabCase, keys, pick } = this.app.lib._
40
+ const { log, readJson, parseObject, getModuleDir, readAllConfigs } = this.app.bajo
41
+ const defKeys = keys(this.config)
42
+ log.trace('- %s', this.ns)
43
+ const dir = this.ns === this.app.mainNs ? (`${this.app.bajo.dir.base}/${this.app.mainNs}`) : getModuleDir(this.pkgName)
44
+ let cfg = await readAllConfigs(`${dir}/config`)
45
+ this.constructor.alias = this.alias ?? (this.pkgName.slice(0, 5) === 'bajo-' ? this.pkgName.slice(5).toLowerCase() : this.ns.toLowerCase())
46
+ this.constructor.alias = kebabCase(this.alias)
47
+
48
+ this.dir = {
49
+ pkg: dir,
50
+ data: `${this.app.bajo.dir.data}/plugins/${this.ns}`
51
+ }
52
+ const file = `${dir + (this.ns === this.app.mainNs ? '/..' : '')}/package.json`
53
+ const pkgJson = await readJson(file)
54
+ this.pkg = pick(pkgJson,
55
+ ['name', 'version', 'description', 'author', 'license', 'homepage'])
56
+ if (this.ns === this.app.mainNs) {
57
+ this.constructor.alias = this.app.mainNs
58
+ this.title = this.title ?? this.alias
59
+ }
60
+ // merge with config from datadir
61
+ try {
62
+ const altCfg = await readAllConfigs(`${this.app.bajo.dir.data}/config/${this.ns}`)
63
+ cfg = defaultsDeep({}, altCfg, cfg)
64
+ } catch (err) {}
65
+ const cfgEnv = get(this, `app.env.${this.ns}`, {})
66
+ const cfgArgv = get(this, `app.argv.${this.ns}`, {})
67
+ const envArgv = defaultsDeep({}, cfgEnv, cfgArgv)
68
+ cfg = pick(defaultsDeep({}, envArgv ?? {}, cfg ?? {}, this.config ?? {}), defKeys)
69
+ this.title = this.title ?? cfg.title ?? this.alias
70
+ this.config = parseObject(cfg, { parseValue: true })
71
+ }
72
+
73
+ /**
74
+ * After config is read, plugin will be initialized. You can still change your config here,
75
+ * because after plugin is initialized, config will be deep frozen.
76
+ *
77
+ * @method
78
+ * @async
79
+ */
80
+ init = async () => {
81
+ }
82
+
83
+ /**
84
+ * This method will be called after plugin's init
85
+ *
86
+ * @method
87
+ * @async
88
+ */
89
+ start = async () => {
90
+ }
91
+
92
+ stop = async () => {
93
+ }
94
+
95
+ /**
96
+ * Upon app termination, this method will be called first. Mostly useful for system cleanup,
97
+ * delete temporary files, freeing resources etc.
98
+ *
99
+ * @method
100
+ * @async
101
+ */
102
+ exit = async () => {
103
+ }
104
+ }
105
+
106
+ export default Base
@@ -1,6 +1,7 @@
1
- import readAllConfigs from '../../lib/read-all-configs.js'
2
1
  import currentLoc from '../../lib/current-loc.js'
3
2
  import resolvePath from '../../lib/resolve-path.js'
3
+ import Print from '../misc/print.js'
4
+ import Log from '../misc/log.js'
4
5
  import omitDeep from 'omit-deep'
5
6
  import os from 'os'
6
7
  import fs from 'fs-extra'
@@ -12,7 +13,7 @@ import {
12
13
  attachMethods,
13
14
  collectHooks,
14
15
  run
15
- } from './plugin.js'
16
+ } from './base.js'
16
17
 
17
18
  const {
18
19
  reduce,
@@ -27,24 +28,32 @@ const {
27
28
  keys,
28
29
  set,
29
30
  get,
30
- isString,
31
31
  filter,
32
32
  trim,
33
33
  without,
34
34
  uniq,
35
35
  camelCase,
36
- isEmpty,
37
- omit
36
+ isEmpty
38
37
  } = lodash
39
38
 
40
39
  const omitted = ['spawn', 'cwd', 'name', 'alias', 'applet', 'a', 'plugins']
41
40
 
42
41
  const defConfig = {
42
+ env: 'dev',
43
43
  log: {
44
- dateFormat: 'YYYY-MM-DDTHH:MM:ss.SSS[Z]',
45
- plain: false,
44
+ timeTaken: false,
45
+ dateFormat: 'YYYY-MM-DDTHH:mm:ss.SSS',
46
+ useUtc: false,
47
+ pretty: false,
46
48
  applet: false,
47
- traceHook: false
49
+ traceHook: false,
50
+ save: false,
51
+ rotation: {
52
+ cycle: 'none', // none, daily, weekly, monthly
53
+ compressOld: true,
54
+ byPlugin: false,
55
+ retain: 5
56
+ }
48
57
  },
49
58
  lang: Intl.DateTimeFormat().resolvedOptions().lang ?? 'en-US',
50
59
  intl: {
@@ -69,83 +78,97 @@ const defConfig = {
69
78
  exitHandler: true
70
79
  }
71
80
 
81
+ const defMain = `async function factory (pkgName) {
82
+ const me = this
83
+
84
+ return class Main extends this.app.pluginClass.base {
85
+ constructor () {
86
+ super(pkgName, me.app)
87
+ this.config = {}
88
+ }
89
+ }
90
+ }
91
+
92
+ export default factory
93
+ `
94
+
72
95
  /**
73
- * @module
96
+ * Internal helpers called by Bajo that only used once for bootstrapping. It should remains
97
+ * hidden and not to be imported by any program.
98
+ *
99
+ * @module Helper/Bajo
74
100
  */
75
101
 
76
102
  /**
77
103
  * Building bajo base config. Mostly dealing with directory setups:
78
104
  * - determine base directory
79
- * - check whether data directory is valid
105
+ * - check whether data directory is valid. If not exist, create one inside app dir
80
106
  * - ensure data config directory is there
107
+ * - ensure tmp dir is there
108
+ * - read the list of plugins from ```.plugins``` file
81
109
  *
82
110
  * @async
83
111
  */
84
112
  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
113
+ // dirs
114
+ const { defaultsDeep } = this.app.lib.aneka
115
+ this.config = defaultsDeep({}, this.app.envVars._, this.app.argv._)
89
116
  set(this, 'dir.base', this.app.dir)
90
117
  const path = currentLoc(import.meta).dir + '/../..'
91
118
  set(this, 'dir.pkg', this.resolvePath(path))
119
+ if (get(this, 'config.dir.data')) set(this, 'dir.data', this.config.dir.data)
92
120
  if (!get(this, 'dir.data')) set(this, 'dir.data', `${this.dir.base}/data`)
93
121
  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
122
  fs.ensureDirSync(`${this.dir.data}/config`)
99
123
  if (!this.dir.tmp) {
100
- this.dir.tmp = `${this.resolvePath(os.tmpdir())}/${this.name}`
124
+ this.dir.tmp = `${this.resolvePath(os.tmpdir())}/${this.ns}`
101
125
  fs.ensureDirSync(this.dir.tmp)
102
126
  }
103
- this.app.addPlugin(this)
127
+ // collect list of plugins
128
+ let pluginPkgs = []
129
+ const pluginsFile = `${this.dir.data}/config/.plugins`
130
+ if (fs.existsSync(pluginsFile)) {
131
+ pluginPkgs = pluginPkgs.concat(filter(map(trim(fs.readFileSync(pluginsFile, 'utf8')).split('\n'), p => trim(p)), b => !isEmpty(b)))
132
+ }
133
+ this.app.pluginPkgs = map(filter(without(uniq(pluginPkgs), this.app.mainNs), p => {
134
+ return p[0] !== '#'
135
+ }), p => {
136
+ return trim(p.split('#')[0])
137
+ })
138
+ this.app.pluginPkgs.push(this.app.mainNs)
104
139
  }
105
140
 
106
141
  /**
107
142
  * Building all plugins:
108
- * - read the list of plugins from ```.plugins``` file
143
+ * - load from app's pluginPkgs
109
144
  * - iterate through the list and build related plugins
145
+ * - making sure main plugin is there. If not, create from template
110
146
  * - attach these plugins to the app instance
111
147
  *
112
148
  * @async
113
149
  */
114
150
  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) {
151
+ this.log.trace('buildPluginsStart')
152
+ for (const pkg of this.app.pluginPkgs) {
128
153
  const ns = camelCase(pkg)
129
154
  let dir
130
155
  if (ns === 'main') {
131
- dir = `${this.dir.base}/${this.mainNs}`
156
+ dir = `${this.dir.base}/${this.app.mainNs}`
132
157
  fs.ensureDirSync(dir)
133
- fs.ensureDirSync(`${dir}/plugin`)
158
+ if (!fs.existsSync(`${dir}/index.js`)) {
159
+ fs.writeFileSync(`${dir}/index.js`, defMain, 'utf8')
160
+ }
134
161
  } else dir = this.getModuleDir(pkg)
135
- let plugin
136
162
  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)
163
+ if (!fs.existsSync(factory)) throw this.error('pluginPackageNotFound%s', pkg)
164
+ const { default: builder } = await import(resolvePath(factory, true))
165
+ const ClassDef = await builder.call(this, pkg)
166
+ const plugin = new ClassDef()
167
+ if (!(plugin instanceof this.app.pluginClass.base)) throw this.error('pluginPackageInvalid%s', pkg)
168
+ this.app.addPlugin(plugin, ClassDef)
169
+ this.log.trace('- ' + pkg)
147
170
  }
148
- this.config = omit(this.config, this.pluginNames)
171
+ this.log.debug('buildPluginsComplete')
149
172
  }
150
173
 
151
174
  /**
@@ -154,7 +177,7 @@ export async function buildPlugins () {
154
177
  * @async
155
178
  */
156
179
  export async function collectConfigHandlers () {
157
- for (const pkg of this.pluginPkgs) {
180
+ for (const pkg of this.app.pluginPkgs) {
158
181
  let dir
159
182
  try {
160
183
  dir = this.getModuleDir(pkg)
@@ -165,8 +188,9 @@ export async function collectConfigHandlers () {
165
188
  if (!mod) continue
166
189
  if (isFunction(mod)) mod = await mod.call(this.app[camelCase(pkg)])
167
190
  if (isPlainObject(mod)) mod = [mod]
168
- this.configHandlers = this.configHandlers.concat(mod)
191
+ this.app.configHandlers = this.app.configHandlers.concat(mod)
169
192
  }
193
+ this.app.log = new Log(this.app)
170
194
  }
171
195
 
172
196
  /**
@@ -179,26 +203,31 @@ export async function collectConfigHandlers () {
179
203
  */
180
204
  export async function buildExtConfig () {
181
205
  // config merging
182
- const { defaultsDeep } = this.lib.aneka
183
- let resp = await readAllConfigs.call(this.app, `${this.dir.data}/config/${this.name}`)
206
+ const { defaultsDeep } = this.app.lib.aneka
207
+ let resp = await this.readAllConfigs(`${this.dir.data}/config/${this.ns}`)
184
208
  resp = omitDeep(pick(resp, ['log', 'exitHandler', 'env']), omitted)
209
+ const envs = this.app.constructor.envs
185
210
  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]
211
+ // language
212
+ this.config.lang = (this.config.lang ?? '').split('.')[0]
213
+ this.app.loadIntl(this.ns)
214
+ this.print = new Print(this)
215
+ // environment
216
+ if (values(envs).includes(this.config.env)) this.config.env = this.app.lib.aneka.getKeyByValue(envs, this.config.env)
217
+ if (!keys(envs).includes(this.config.env)) throw this.error('unknownEnv%s%s', this.config.env, this.join(keys(envs), { lastSeparator: this.t('or') }))
218
+ process.env.NODE_ENV = envs[this.config.env]
190
219
  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')
220
+ // misc
221
+ const obj = this.app.applet ? this.config : pick(this.config, keys(defConfig))
222
+ this.config = this.parseObject(obj, { parseValue: true })
223
+ const exts = this.app.getConfigFormats()
224
+ if (this.app.applet) {
225
+ if (!this.app.pluginPkgs.includes('bajo-cli')) throw this.error('appletNeedsBajoCli')
194
226
  if (!this.config.log.applet) this.config.log.level = 'silent'
195
227
  this.config.exitHandler = false
196
228
  }
197
- const exts = map(this.configHandlers, 'ext')
198
- this.initPrint()
199
- this.initLog()
229
+ this.log.trace('dataDir%s', this.dir.data)
200
230
  this.log.debug('configHandlers%s', this.join(exts))
201
- this.config = this.parseObject(this.config, { parseValue: true })
202
231
  }
203
232
 
204
233
  /**
@@ -208,17 +237,17 @@ export async function buildExtConfig () {
208
237
  */
209
238
  export async function bootOrder () {
210
239
  this.log.debug('setupBootOrder')
211
- const order = reduce(this.pluginPkgs, (o, k, i) => {
240
+ const order = reduce(this.app.pluginPkgs, (o, k, i) => {
212
241
  const key = map(k.split(':'), m => trim(m))
213
242
  if (key[1] && !isNaN(Number(key[1]))) o[key[0]] = Number(key[1])
214
243
  else o[key[0]] = 10000 + i
215
244
  return o
216
245
  }, {})
217
246
  const norder = {}
218
- for (let n of this.pluginPkgs) {
247
+ for (let n of this.app.pluginPkgs) {
219
248
  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)
249
+ const dir = n === this.app.mainNs ? (`${this.dir.base}/${this.app.mainNs}`) : this.getModuleDir(n)
250
+ if (n !== this.app.mainNs && !fs.existsSync(dir)) throw this.error('packageNotFoundOrNotBajo%s', n)
222
251
  norder[n] = NaN
223
252
  try {
224
253
  norder[n] = Number(trim(await fs.readFile(`${dir}/.bootorder`, 'utf8')))
@@ -229,20 +258,21 @@ export async function bootOrder () {
229
258
  const item = { k, v: isNaN(norder[k]) ? v : norder[k] }
230
259
  result.push(item)
231
260
  })
232
- this.pluginPkgs = map(orderBy(result, ['v']), 'k')
233
- this.log.info('runInEnv%s', this.print.write(this.envs[this.config.env]))
261
+ this.app.pluginPkgs = map(orderBy(result, ['v']), 'k')
262
+ this.log.debug('runInEnv%s', this.t(this.app.constructor.envs[this.config.env]))
234
263
  // misc
235
264
  this.freeze(this.config)
236
265
  }
237
266
 
238
267
  /**
239
268
  * 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}
269
+ *
270
+ * 1. {@link module:Helper/Base.buildConfigs|build configs}
271
+ * 2. {@link module:Helper/Base.checkNameAliases|ensure names & aliases uniqueness}
272
+ * 3. {@link module:Helper/Base.checkDependencies|ensure dependencies are met}
273
+ * 4. {@link module:Helper/Base.attachMethods|build and attach dynamic methods}
274
+ * 5. {@link module:Helper/Base.collectHooks|collect hooks}
275
+ * 6. {@link module:Helper/Base.run|run plugins}
246
276
  *
247
277
  * @async
248
278
  */
@@ -265,10 +295,10 @@ export async function exitHandler () {
265
295
 
266
296
  async function exit (signal) {
267
297
  const { eachPlugins } = this
268
- this.log.warn('signalReceived%s', signal)
269
- await eachPlugins(async function () {
298
+ if (signal) this.log.warn('signalReceived%s', signal)
299
+ await eachPlugins(async function ({ ns }) {
270
300
  try {
271
- await this.stop()
301
+ await this.exit()
272
302
  } catch (err) {}
273
303
  this.log.trace('exited')
274
304
  })
@@ -284,6 +314,10 @@ export async function exitHandler () {
284
314
  await exit.call(this, 'SIGTERM')
285
315
  })
286
316
 
317
+ process.on('beforeExit', async () => {
318
+ await exit.call(this)
319
+ })
320
+
287
321
  process.on('uncaughtException', (error, origin) => {
288
322
  setTimeout(() => {
289
323
  console.error(error)
@@ -316,29 +350,51 @@ export async function exitHandler () {
316
350
  * If app is in ```applet``` mode, this little helper should take care plugin's applet boot process
317
351
  *
318
352
  * @async
353
+ * @fires {ns}:beforeAppletRun
354
+ * @fires {ns}:afterAppletRun
319
355
  */
320
356
  export async function runAsApplet () {
321
- const { isString, map, find } = this.lib._
357
+ const { isString, map, find } = this.app.lib._
322
358
  await this.eachPlugins(async function ({ file }) {
323
- const { name: ns, alias } = this
324
- this.app.bajo.applets.push({ ns, file, alias })
359
+ const { ns } = this
360
+ const { alias } = this.constructor
361
+ this.app.applets.push({ ns, file, alias })
325
362
  }, { glob: 'applet.js', prefix: 'bajoCli' })
326
363
 
327
364
  this.log.debug('appletModeActivated')
328
365
  this.print.info('appRunningAsApplet')
329
- if (this.applets.length === 0) this.print.fatal('noAppletLoaded')
330
- let name = this.applet
331
- if (!isString(this.applet)) {
366
+ if (this.app.applets.length === 0) this.print.fatal('noAppletLoaded')
367
+ let name = this.app.applet
368
+ if (!isString(name)) {
332
369
  const select = await this.importPkg('bajoCli:@inquirer/select')
333
370
  name = await select({
334
- message: this.print.write('Please select:'),
335
- choices: map(this.applets, t => ({ value: t.ns }))
371
+ message: this.t('Please select:'),
372
+ choices: map(this.app.applets, t => ({ value: t.ns }))
336
373
  })
337
374
  }
338
375
  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`)
376
+ const applet = find(this.app.applets, a => (a.ns === ns || a.alias === ns))
377
+ if (!applet) this.print.fatal('notFound%s%s', this.app.t('applet'), name)
378
+
379
+ /**
380
+ * Run before applet is run. ```[ns]``` is applet's namespace
381
+ *
382
+ * @global
383
+ * @event {ns}:beforeAppletRun
384
+ * @param {...any} params
385
+ * @see {@tutorial hook}
386
+ * @see module:Helper/Bajo.runAsApplet
387
+ */
388
+ await this.runHook(`${this.app[applet.ns]}:beforeAppletRun`, ...this.app.args)
342
389
  await this.app.bajoCli.runApplet(applet, path, ...this.app.args)
343
- await this.runHook(`${this.app[applet.ns]}:afterAppletRun`)
390
+ /**
391
+ * Run after applet is run. ```[ns]``` is applet's namespace
392
+ *
393
+ * @global
394
+ * @event {ns}:afterAppletRun
395
+ * @param {...any} params
396
+ * @see {@tutorial hook}
397
+ * @see module:Helper/Bajo.runAsApplet
398
+ */
399
+ await this.runHook(`${this.app[applet.ns]}:afterAppletRun`, ...this.app.args)
344
400
  }