bajo 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.github/FUNDING.yml +13 -0
  2. package/.github/workflows/repo-lockdown.yml +24 -0
  3. package/.jsdoc.conf.json +7 -6
  4. package/LICENSE +1 -1
  5. package/README.md +50 -18
  6. package/class/app.js +376 -54
  7. package/class/bajo.js +228 -149
  8. package/class/base.js +106 -0
  9. package/class/helper/bajo.js +136 -85
  10. package/class/helper/{plugin.js → base.js} +96 -22
  11. package/class/{base → misc}/err.js +44 -14
  12. package/class/misc/log.js +212 -0
  13. package/class/misc/print.js +264 -0
  14. package/class/plugin.js +120 -68
  15. package/docs/App.html +23 -1
  16. package/docs/Bajo.html +2 -9
  17. package/docs/Base.html +3 -0
  18. package/docs/Err.html +3 -1
  19. package/docs/Log.html +5 -1
  20. package/docs/Plugin.html +1 -1
  21. package/docs/Print.html +1 -1
  22. package/docs/class_app.js.html +378 -56
  23. package/docs/class_bajo.js.html +230 -151
  24. package/docs/class_base.js.html +109 -0
  25. package/docs/class_helper_bajo.js.html +138 -87
  26. package/docs/class_helper_base.js.html +246 -0
  27. package/docs/class_misc_err.js.html +129 -0
  28. package/docs/class_misc_log.js.html +210 -0
  29. package/docs/class_misc_print.js.html +267 -0
  30. package/docs/class_plugin.js.html +122 -70
  31. package/docs/data/search.json +1 -1
  32. package/docs/global.html +4 -1
  33. package/docs/index.html +2 -2
  34. package/docs/index.js.html +35 -0
  35. package/docs/lib_current-loc.js.html +36 -0
  36. package/docs/lib_formats.js.html +8 -8
  37. package/docs/lib_import-module.js.html +59 -0
  38. package/docs/lib_log-levels.js.html +17 -7
  39. package/docs/lib_parse-args-argv.js.html +83 -0
  40. package/docs/lib_parse-env.js.html +53 -0
  41. package/docs/lib_resolve-path.js.html +3 -6
  42. package/docs/lib_shim.js.html +8 -3
  43. package/docs/module-Helper_Bajo.html +3 -0
  44. package/docs/module-Helper_Base.html +3 -0
  45. package/docs/module-Lib.html +15 -0
  46. package/docs/static/home.md +32 -0
  47. package/docs/static/logo-ecosystem.png +0 -0
  48. package/docs/static/logo.png +0 -0
  49. package/extend/bajo/intl/en-US.json +8 -2
  50. package/extend/bajo/intl/id.json +8 -2
  51. package/index.js +22 -2
  52. package/lib/current-loc.js +24 -2
  53. package/lib/formats.js +6 -6
  54. package/lib/import-module.js +29 -0
  55. package/lib/log-levels.js +15 -5
  56. package/lib/parse-args-argv.js +20 -12
  57. package/lib/parse-env.js +18 -7
  58. package/lib/resolve-path.js +1 -4
  59. package/lib/shim.js +6 -1
  60. package/package.json +4 -7
  61. package/wiki/CONFIG.md +30 -0
  62. package/wiki/CONTRIBUTING.md +5 -0
  63. package/wiki/DEV_GUIDE.md +3 -0
  64. package/wiki/ECOSYSTEM.md +93 -0
  65. package/wiki/GETTING-STARTED.md +356 -0
  66. package/wiki/USER-GUIDE.md +256 -0
  67. package/class/base/log.js +0 -205
  68. package/class/base/plugin.js +0 -177
  69. package/class/base/print.js +0 -272
  70. package/docs/BasePlugin.html +0 -5
  71. package/docs/class_base_err.js.html +0 -99
  72. package/docs/class_base_log.js.html +0 -208
  73. package/docs/class_base_plugin.js.html +0 -180
  74. package/docs/class_base_print.js.html +0 -275
  75. package/docs/class_helper_plugin.js.html +0 -172
  76. package/docs/lib_create-method.js.html +0 -42
  77. package/docs/module-class_helper_bajo.html +0 -3
  78. package/docs/module-class_helper_plugin.html +0 -3
  79. package/docs/module-lib_create-method.html +0 -3
  80. package/docs/module-lib_formats.html +0 -3
  81. package/docs/module-lib_log-levels.html +0 -3
  82. package/docs/module-lib_resolve-path.html +0 -3
  83. package/docs/module-lib_shim.html +0 -3
  84. package/docs/tutorial-contribution.html +0 -3
  85. package/docs/tutorial-ecosystem.html +0 -3
  86. package/docs/tutorial-getting-started.html +0 -13
  87. package/docs/tutorial-plugin-dev.html +0 -3
  88. package/docs/tutorial-user-guide.html +0 -3
  89. package/lib/create-method.js +0 -39
  90. package/lib/dayjs.js +0 -8
  91. package/lib/omitted-plugin-keys.js +0 -3
  92. package/lib/read-all-configs.js +0 -19
  93. package/misc-docs/.hook.md +0 -11
  94. package/misc-docs/bitcoin.jpeg +0 -0
  95. package/misc-docs/contribution.md +0 -20
  96. package/misc-docs/ecosystem.md +0 -94
  97. package/misc-docs/getting-started.md +0 -142
  98. package/misc-docs/plugin-dev.md +0 -0
  99. package/misc-docs/toc.json +0 -17
  100. package/misc-docs/user-guide.md +0 -1
  101. /package/docs/{bitcoin.jpeg → static/bitcoin.jpeg} +0 -0
package/class/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,22 +28,23 @@ 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
+ timeTaken: false,
44
45
  dateFormat: 'YYYY-MM-DDTHH:MM:ss.SSS[Z]',
45
- plain: false,
46
+ localDate: false,
47
+ pretty: false,
46
48
  applet: false,
47
49
  traceHook: false
48
50
  },
@@ -69,25 +71,43 @@ const defConfig = {
69
71
  exitHandler: true
70
72
  }
71
73
 
74
+ const defMain = `async function factory (pkgName) {
75
+ const me = this
76
+
77
+ return class Main extends this.app.pluginClass.base {
78
+ constructor () {
79
+ super(pkgName, me.app)
80
+ this.config = {}
81
+ }
82
+ }
83
+ }
84
+
85
+ export default factory
86
+ `
87
+
72
88
  /**
73
- * @module
89
+ * Internal helpers called by Bajo that only used once for bootstrapping. It should remains
90
+ * hidden and not to be imported by any program.
91
+ *
92
+ * @module Helper/Bajo
74
93
  */
75
94
 
76
95
  /**
77
96
  * Building bajo base config. Mostly dealing with directory setups:
78
97
  * - determine base directory
79
- * - check whether data directory is valid
98
+ * - check whether data directory is valid. If not exist, create one inside app dir
80
99
  * - ensure data config directory is there
100
+ * - ensure tmp dir is there
101
+ * - read the list of plugins from ```.plugins``` file
81
102
  *
82
103
  * @async
83
104
  */
84
105
  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
106
+ // dirs
107
+ const { defaultsDeep } = this.app.lib.aneka
108
+ this.config = defaultsDeep({}, this.app.envVars._, this.app.argv._)
89
109
  set(this, 'dir.base', this.app.dir)
90
- const path = currentLoc(import.meta).dir + '/../../..'
110
+ const path = currentLoc(import.meta).dir + '/../..'
91
111
  set(this, 'dir.pkg', this.resolvePath(path))
92
112
  if (!get(this, 'dir.data')) set(this, 'dir.data', `${this.dir.base}/data`)
93
113
  this.dir.data = this.resolvePath(this.dir.data)
@@ -97,55 +117,54 @@ export async function buildBaseConfig () {
97
117
  }
98
118
  fs.ensureDirSync(`${this.dir.data}/config`)
99
119
  if (!this.dir.tmp) {
100
- this.dir.tmp = `${this.resolvePath(os.tmpdir())}/${this.name}`
120
+ this.dir.tmp = `${this.resolvePath(os.tmpdir())}/${this.ns}`
101
121
  fs.ensureDirSync(this.dir.tmp)
102
122
  }
103
- this.app.addPlugin(this)
123
+ // collect list of plugins
124
+ let pluginPkgs = []
125
+ const pluginsFile = `${this.dir.data}/config/.plugins`
126
+ if (fs.existsSync(pluginsFile)) {
127
+ pluginPkgs = pluginPkgs.concat(filter(map(trim(fs.readFileSync(pluginsFile, 'utf8')).split('\n'), p => trim(p)), b => !isEmpty(b)))
128
+ }
129
+ this.app.pluginPkgs = map(filter(without(uniq(pluginPkgs), this.app.mainNs), p => {
130
+ return p[0] !== '#'
131
+ }), p => {
132
+ return trim(p.split('#')[0])
133
+ })
134
+ this.app.pluginPkgs.push(this.app.mainNs)
104
135
  }
105
136
 
106
137
  /**
107
138
  * Building all plugins:
108
- * - read the list of plugins from ```.plugins``` file
139
+ * - load from app's pluginPkgs
109
140
  * - iterate through the list and build related plugins
141
+ * - making sure main plugin is there. If not, create from template
110
142
  * - attach these plugins to the app instance
111
143
  *
112
144
  * @async
113
145
  */
114
146
  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) {
147
+ this.log.trace('buildPluginsStart')
148
+ for (const pkg of this.app.pluginPkgs) {
128
149
  const ns = camelCase(pkg)
129
150
  let dir
130
151
  if (ns === 'main') {
131
- dir = `${this.dir.base}/${this.mainNs}`
152
+ dir = `${this.dir.base}/${this.app.mainNs}`
132
153
  fs.ensureDirSync(dir)
133
- fs.ensureDirSync(`${dir}/plugin`)
154
+ if (!fs.existsSync(`${dir}/index.js`)) {
155
+ fs.writeFileSync(`${dir}/index.js`, defMain, 'utf8')
156
+ }
134
157
  } else dir = this.getModuleDir(pkg)
135
- let plugin
136
158
  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)
159
+ if (!fs.existsSync(factory)) throw this.error('pluginPackageNotFound%s', pkg)
160
+ const { default: builder } = await import(resolvePath(factory, true))
161
+ const ClassDef = await builder.call(this, pkg)
162
+ const plugin = new ClassDef()
163
+ if (!(plugin instanceof this.app.pluginClass.base)) throw this.error('pluginPackageInvalid%s', pkg)
164
+ this.app.addPlugin(plugin, ClassDef)
165
+ this.log.trace('- ' + pkg)
147
166
  }
148
- this.config = omit(this.config, this.pluginNames)
167
+ this.log.debug('buildPluginsComplete')
149
168
  }
150
169
 
151
170
  /**
@@ -154,7 +173,7 @@ export async function buildPlugins () {
154
173
  * @async
155
174
  */
156
175
  export async function collectConfigHandlers () {
157
- for (const pkg of this.pluginPkgs) {
176
+ for (const pkg of this.app.pluginPkgs) {
158
177
  let dir
159
178
  try {
160
179
  dir = this.getModuleDir(pkg)
@@ -165,8 +184,9 @@ export async function collectConfigHandlers () {
165
184
  if (!mod) continue
166
185
  if (isFunction(mod)) mod = await mod.call(this.app[camelCase(pkg)])
167
186
  if (isPlainObject(mod)) mod = [mod]
168
- this.configHandlers = this.configHandlers.concat(mod)
187
+ this.app.configHandlers = this.app.configHandlers.concat(mod)
169
188
  }
189
+ this.app.log = new Log(this.app)
170
190
  }
171
191
 
172
192
  /**
@@ -179,26 +199,30 @@ export async function collectConfigHandlers () {
179
199
  */
180
200
  export async function buildExtConfig () {
181
201
  // config merging
182
- const { defaultsDeep } = this.lib.aneka
183
- let resp = await readAllConfigs.call(this.app, `${this.dir.data}/config/${this.name}`)
202
+ const { defaultsDeep } = this.app.lib.aneka
203
+ let resp = await this.readAllConfigs(`${this.dir.data}/config/${this.ns}`)
184
204
  resp = omitDeep(pick(resp, ['log', 'exitHandler', 'env']), omitted)
205
+ const envs = this.app.constructor.envs
185
206
  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]
207
+ // language
208
+ this.config.lang = (this.config.lang ?? '').split('.')[0]
209
+ this.app.loadIntl(this.ns)
210
+ this.print = new Print(this)
211
+ // environment
212
+ if (values(envs).includes(this.config.env)) this.config.env = this.app.lib.aneka.getKeyByValue(envs, this.config.env)
213
+ if (!keys(envs).includes(this.config.env)) throw this.error('unknownEnv%s%s', this.config.env, this.join(keys(envs), { lastSeparator: this.t('or') }))
214
+ process.env.NODE_ENV = envs[this.config.env]
190
215
  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')
216
+ // misc
217
+ const obj = this.app.applet ? this.config : pick(this.config, keys(defConfig))
218
+ this.config = this.parseObject(obj, { parseValue: true })
219
+ const exts = this.app.getConfigFormats()
220
+ if (this.app.applet) {
221
+ if (!this.app.pluginPkgs.includes('bajo-cli')) throw this.error('appletNeedsBajoCli')
194
222
  if (!this.config.log.applet) this.config.log.level = 'silent'
195
223
  this.config.exitHandler = false
196
224
  }
197
- const exts = map(this.configHandlers, 'ext')
198
- this.initPrint()
199
- this.initLog()
200
225
  this.log.debug('configHandlers%s', this.join(exts))
201
- this.config = this.parseObject(this.config, { parseValue: true })
202
226
  }
203
227
 
204
228
  /**
@@ -208,17 +232,17 @@ export async function buildExtConfig () {
208
232
  */
209
233
  export async function bootOrder () {
210
234
  this.log.debug('setupBootOrder')
211
- const order = reduce(this.pluginPkgs, (o, k, i) => {
235
+ const order = reduce(this.app.pluginPkgs, (o, k, i) => {
212
236
  const key = map(k.split(':'), m => trim(m))
213
237
  if (key[1] && !isNaN(Number(key[1]))) o[key[0]] = Number(key[1])
214
238
  else o[key[0]] = 10000 + i
215
239
  return o
216
240
  }, {})
217
241
  const norder = {}
218
- for (let n of this.pluginPkgs) {
242
+ for (let n of this.app.pluginPkgs) {
219
243
  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)
244
+ const dir = n === this.app.mainNs ? (`${this.dir.base}/${this.app.mainNs}`) : this.getModuleDir(n)
245
+ if (n !== this.app.mainNs && !fs.existsSync(dir)) throw this.error('packageNotFoundOrNotBajo%s', n)
222
246
  norder[n] = NaN
223
247
  try {
224
248
  norder[n] = Number(trim(await fs.readFile(`${dir}/.bootorder`, 'utf8')))
@@ -229,20 +253,21 @@ export async function bootOrder () {
229
253
  const item = { k, v: isNaN(norder[k]) ? v : norder[k] }
230
254
  result.push(item)
231
255
  })
232
- this.pluginPkgs = map(orderBy(result, ['v']), 'k')
233
- this.log.info('runInEnv%s', this.print.write(this.envs[this.config.env]))
256
+ this.app.pluginPkgs = map(orderBy(result, ['v']), 'k')
257
+ this.log.debug('runInEnv%s', this.t(this.app.constructor.envs[this.config.env]))
234
258
  // misc
235
259
  this.freeze(this.config)
236
260
  }
237
261
 
238
262
  /**
239
263
  * 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}
264
+ *
265
+ * 1. {@link module:Helper/Base.buildConfigs|build configs}
266
+ * 2. {@link module:Helper/Base.checkNameAliases|ensure names & aliases uniqueness}
267
+ * 3. {@link module:Helper/Base.checkDependencies|ensure dependencies are met}
268
+ * 4. {@link module:Helper/Base.attachMethods|build and attach dynamic methods}
269
+ * 5. {@link module:Helper/Base.collectHooks|collect hooks}
270
+ * 6. {@link module:Helper/Base.run|run plugins}
246
271
  *
247
272
  * @async
248
273
  */
@@ -265,10 +290,10 @@ export async function exitHandler () {
265
290
 
266
291
  async function exit (signal) {
267
292
  const { eachPlugins } = this
268
- this.log.warn('signalReceived%s', signal)
269
- await eachPlugins(async function () {
293
+ if (signal) this.log.warn('signalReceived%s', signal)
294
+ await eachPlugins(async function ({ ns }) {
270
295
  try {
271
- await this.stop()
296
+ await this.exit()
272
297
  } catch (err) {}
273
298
  this.log.trace('exited')
274
299
  })
@@ -284,6 +309,10 @@ export async function exitHandler () {
284
309
  await exit.call(this, 'SIGTERM')
285
310
  })
286
311
 
312
+ process.on('beforeExit', async () => {
313
+ await exit.call(this)
314
+ })
315
+
287
316
  process.on('uncaughtException', (error, origin) => {
288
317
  setTimeout(() => {
289
318
  console.error(error)
@@ -316,29 +345,51 @@ export async function exitHandler () {
316
345
  * If app is in ```applet``` mode, this little helper should take care plugin's applet boot process
317
346
  *
318
347
  * @async
348
+ * @fires {ns}:beforeAppletRun
349
+ * @fires {ns}:afterAppletRun
319
350
  */
320
351
  export async function runAsApplet () {
321
- const { isString, map, find } = this.lib._
352
+ const { isString, map, find } = this.app.lib._
322
353
  await this.eachPlugins(async function ({ file }) {
323
- const { name: ns, alias } = this
324
- this.app.bajo.applets.push({ ns, file, alias })
354
+ const { ns } = this
355
+ const { alias } = this.constructor
356
+ this.app.applets.push({ ns, file, alias })
325
357
  }, { glob: 'applet.js', prefix: 'bajoCli' })
326
358
 
327
359
  this.log.debug('appletModeActivated')
328
360
  this.print.info('appRunningAsApplet')
329
- if (this.applets.length === 0) this.print.fatal('noAppletLoaded')
330
- let name = this.applet
331
- if (!isString(this.applet)) {
361
+ if (this.app.applets.length === 0) this.print.fatal('noAppletLoaded')
362
+ let name = this.app.applet
363
+ if (!isString(name)) {
332
364
  const select = await this.importPkg('bajoCli:@inquirer/select')
333
365
  name = await select({
334
- message: this.print.write('Please select:'),
335
- choices: map(this.applets, t => ({ value: t.ns }))
366
+ message: this.t('Please select:'),
367
+ choices: map(this.app.applets, t => ({ value: t.ns }))
336
368
  })
337
369
  }
338
370
  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`)
371
+ const applet = find(this.app.applets, a => (a.ns === ns || a.alias === ns))
372
+ if (!applet) this.print.fatal('notFound%s%s', this.app.t('applet'), name)
373
+
374
+ /**
375
+ * Run before applet is run. ```[ns]``` is applet's namespace
376
+ *
377
+ * @global
378
+ * @event {ns}:beforeAppletRun
379
+ * @param {...any} params
380
+ * @see {@tutorial hook}
381
+ * @see module:Helper/Bajo.runAsApplet
382
+ */
383
+ await this.runHook(`${this.app[applet.ns]}:beforeAppletRun`, ...this.app.args)
342
384
  await this.app.bajoCli.runApplet(applet, path, ...this.app.args)
343
- await this.runHook(`${this.app[applet.ns]}:afterAppletRun`)
385
+ /**
386
+ * Run after applet is run. ```[ns]``` is applet's namespace
387
+ *
388
+ * @global
389
+ * @event {ns}:afterAppletRun
390
+ * @param {...any} params
391
+ * @see {@tutorial hook}
392
+ * @see module:Helper/Bajo.runAsApplet
393
+ */
394
+ await this.runHook(`${this.app[applet.ns]}:afterAppletRun`, ...this.app.args)
344
395
  }