bajo 2.23.0 → 2.25.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.
- package/.jsdoc.conf.json +2 -1
- package/class/app.js +220 -107
- package/class/bajo.js +257 -177
- package/class/base.js +85 -24
- package/class/cache.js +8 -16
- package/class/err.js +31 -10
- package/class/log.js +46 -38
- package/class/plugin.js +53 -30
- package/class/print.js +18 -15
- package/class/tools.js +17 -7
- package/docs/App.html +51 -1
- package/docs/Bajo.html +1 -1
- package/docs/Base.html +34 -1
- package/docs/Cache.html +1 -1
- package/docs/Err.html +2 -2
- package/docs/Log.html +2 -2
- package/docs/Main.html +3 -0
- package/docs/Plugin.html +1 -1
- package/docs/Print.html +1 -1
- package/docs/Tools.html +1 -1
- package/docs/class_app.js.html +222 -109
- package/docs/class_bajo.js.html +260 -179
- package/docs/class_base.js.html +87 -26
- package/docs/class_cache.js.html +10 -18
- package/docs/class_err.js.html +33 -12
- package/docs/class_log.js.html +48 -40
- package/docs/class_plugin.js.html +55 -32
- package/docs/class_print.js.html +20 -17
- package/docs/class_tools.js.html +19 -9
- package/docs/data/search.json +1 -1
- package/docs/global.html +22 -1
- package/docs/index.html +1 -1
- package/docs/index.js.html +53 -16
- package/docs/lib_applet-hook.js.html +328 -0
- package/docs/lib_config.js.html +167 -0
- package/docs/lib_helper.js.html +186 -192
- package/docs/module-Applet.html +3 -0
- package/docs/module-Helper.html +1 -8
- package/docs/module-Hook.html +1 -1
- package/docs/tutorial-01_WELCOME.html +3 -0
- package/docs/tutorial-02_GETTING_STARTED.html +125 -0
- package/docs/tutorial-03_USER_GUIDE.html +54 -0
- package/docs/tutorial-04_DEV_GUIDE.html +178 -0
- package/docs/tutorial-05_ECOSYSTEM.html +3 -0
- package/extend/bajo/intl/en-US.json +2 -1
- package/extend/bajo/intl/id.json +2 -1
- package/index.js +51 -14
- package/lib/applet-hook.js +325 -0
- package/lib/config.js +164 -0
- package/lib/helper.js +185 -191
- package/package.json +6 -2
- package/test/00-global-hooks.test.js +44 -0
- package/test/e2e/_run.js +19 -0
- package/test/e2e/e2e-applet-process.test.js +66 -0
- package/test/e2e/e2e-boot-process.test.js +62 -0
- package/test/e2e/e2e-hooks-process.test.js +68 -0
- package/test/integration/_setup.js +55 -0
- package/test/integration/aspect-01-boot-instance.test.js +24 -0
- package/test/integration/aspect-02-plugin-loading.test.js +24 -0
- package/test/integration/aspect-03-config-merge.test.js +27 -0
- package/test/integration/aspect-04-intl-loading.test.js +29 -0
- package/test/integration/aspect-05-cache-roundtrip.test.js +26 -0
- package/test/integration/aspect-06-log-file-save.test.js +28 -0
- package/test/integration/aspect-07-plugin-data-dir.test.js +25 -0
- package/test/integration/aspect-08-plugin-lookup.test.js +25 -0
- package/test/integration/aspect-09-config-formats.test.js +26 -0
- package/test/integration/aspect-10-hooks.test.js +38 -0
- package/test/unit/app.test.js +115 -0
- package/test/unit/applet-hook.test.js +10 -0
- package/test/unit/bajo.test.js +151 -0
- package/test/unit/base.test.js +90 -0
- package/test/unit/cache.test.js +89 -0
- package/test/unit/config.test.js +14 -0
- package/test/unit/err.test.js +57 -0
- package/test/unit/helper.test.js +116 -0
- package/test/unit/index.test.js +40 -0
- package/test/unit/log.test.js +84 -0
- package/test/unit/plugin.test.js +95 -0
- package/test/unit/print.test.js +72 -0
- package/test/unit/tools.test.js +29 -0
- package/wiki/CHANGES.md +10 -0
- package/wiki/tutorials/01_WELCOME.md +18 -0
- package/wiki/tutorials/02_GETTING_STARTED.md +366 -0
- package/wiki/tutorials/03_USER_GUIDE.md +262 -0
- package/wiki/tutorials/04_DEV_GUIDE.md +386 -0
- package/wiki/tutorials/05_ECOSYSTEM.md +95 -0
- package/wiki/tutorials/tutorials.json +17 -0
- package/docs/lib_hook.js.html +0 -231
- package/docs/module-Lib.html +0 -3
- package/lib/hook.js +0 -228
- package/test/app.test.js +0 -183
- package/test/bajo.test.js +0 -124
- package/test/base.test.js +0 -75
- package/test/cache.test.js +0 -94
- package/test/e2e.test.js +0 -137
- package/test/err.test.js +0 -73
- package/test/helper.test.js +0 -39
- package/test/import-module.test.js +0 -138
- package/test/integration.test.js +0 -218
- package/test/log.test.js +0 -119
- package/test/plugin.test.js +0 -116
- package/test/print.test.js +0 -100
- package/test/tools.test.js +0 -38
- package/wiki/CONFIG.md +0 -36
- package/wiki/DEV_GUIDE.md +0 -3
- package/wiki/ECOSYSTEM.md +0 -93
- package/wiki/GETTING-STARTED.md +0 -356
- package/wiki/USER-GUIDE.md +0 -256
package/class/bajo.js
CHANGED
|
@@ -13,11 +13,14 @@ import * as yaml from 'js-yaml'
|
|
|
13
13
|
import aneka from 'aneka'
|
|
14
14
|
import {
|
|
15
15
|
buildBaseConfig,
|
|
16
|
-
|
|
16
|
+
buildConfig,
|
|
17
17
|
buildPlugins,
|
|
18
18
|
collectConfigHandlers,
|
|
19
19
|
bootOrder,
|
|
20
|
-
|
|
20
|
+
checkNameAliases,
|
|
21
|
+
checkDependencies,
|
|
22
|
+
collectHooks,
|
|
23
|
+
runPlugins,
|
|
21
24
|
exitHandler,
|
|
22
25
|
importModule,
|
|
23
26
|
types as formatTypes,
|
|
@@ -27,20 +30,20 @@ import {
|
|
|
27
30
|
const require = createRequire(import.meta.url)
|
|
28
31
|
|
|
29
32
|
const {
|
|
30
|
-
isFunction, map, isObject,
|
|
33
|
+
isFunction, map, isObject, findIndex, uniq, merge, mergeWith,
|
|
31
34
|
trim, filter, isEmpty, orderBy, pullAt, find, camelCase,
|
|
32
35
|
cloneDeep, isPlainObject, isArray, isString, omit, keys, indexOf,
|
|
33
|
-
last, get, has, values, pick
|
|
36
|
+
last, get, has, values, pick, isBoolean
|
|
34
37
|
} = lodash
|
|
35
38
|
|
|
36
|
-
const { resolvePath, getGlobalModuleDir } = aneka
|
|
39
|
+
const { resolvePath, getGlobalModuleDir, defaultsDeep, isSet, parseObject } = aneka
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
|
-
* Name
|
|
42
|
+
* Name in `{ns}:{path}` format.
|
|
40
43
|
*
|
|
41
44
|
* @typedef {string} TNsPathPairs
|
|
42
45
|
* @memberof Bajo
|
|
43
|
-
* @see TNsPathResult
|
|
46
|
+
* @see Bajo.TNsPathResult
|
|
44
47
|
* @see Bajo#buildNsPath
|
|
45
48
|
* @see Bajo#breakNsPath
|
|
46
49
|
*/
|
|
@@ -56,23 +59,30 @@ const { resolvePath, getGlobalModuleDir } = aneka
|
|
|
56
59
|
*/
|
|
57
60
|
|
|
58
61
|
/**
|
|
59
|
-
* Object returned by {@link Bajo#breakNsPath|bajo:breakNsPath}.
|
|
62
|
+
* Object returned by {@link Bajo#breakNsPath|bajo:breakNsPath()}.
|
|
60
63
|
*
|
|
61
64
|
* @typedef {Object} TNsPathResult
|
|
62
65
|
* @memberof Bajo
|
|
63
|
-
* @property {string} ns - Namespace
|
|
66
|
+
* @property {string} [ns] - Namespace
|
|
64
67
|
* @property {string} [subNs] - Sub namespace
|
|
65
68
|
* @property {string} [subSubNs] - Sub of sub namespace
|
|
69
|
+
* @property {string} [fullNs] - Full namespace, including sub namespaces
|
|
66
70
|
* @property {string} path - Path without query string or hash
|
|
67
|
-
* @property {string} fullPath - Full path, including query string and hash
|
|
68
|
-
* @
|
|
71
|
+
* @property {string} [fullPath] - Full path, including query string and hash
|
|
72
|
+
* @property {string} [realPath] - Path without query string or hash, but with parameters replaced with their values
|
|
73
|
+
* @property {string} [realFullPath] - Full path, including query string and hash, but with parameters replaced with their values
|
|
74
|
+
* @property {Object} [qs] - Query string object
|
|
75
|
+
* @property {Object} [params] - Parameters object
|
|
76
|
+
* @see Bajo.TNsPathPairs
|
|
69
77
|
* @see Bajo#buildNsPath
|
|
70
78
|
* @see Bajo#breakNsPath
|
|
71
79
|
*/
|
|
72
80
|
|
|
73
81
|
/**
|
|
74
|
-
* The
|
|
75
|
-
*
|
|
82
|
+
* The core plugin. The main engine. The special plugin that controls the app's boot process and
|
|
83
|
+
* makes sure all other plugins work nicely.
|
|
84
|
+
*
|
|
85
|
+
* Don't create your own instance of this class or {@link App} class. Instead, use the {@link boot} process.
|
|
76
86
|
*
|
|
77
87
|
* @class
|
|
78
88
|
*/
|
|
@@ -86,57 +96,61 @@ class Bajo extends Plugin {
|
|
|
86
96
|
super('bajo', app)
|
|
87
97
|
|
|
88
98
|
/**
|
|
89
|
-
* Alias
|
|
99
|
+
* Alias. Read-only
|
|
90
100
|
* @type {string}
|
|
101
|
+
* @default 'bajo'
|
|
91
102
|
*/
|
|
92
103
|
this.alias = 'bajo'
|
|
93
104
|
|
|
94
105
|
/**
|
|
95
|
-
*
|
|
106
|
+
* Array of white space characters. Used to trim strings
|
|
96
107
|
* @type {string[]}
|
|
97
108
|
*/
|
|
98
109
|
this.whiteSpace = [' ', '\t', '\n', '\r']
|
|
99
110
|
|
|
100
111
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* @type {Bajo.TObject}
|
|
112
|
+
* Configuration object
|
|
113
|
+
* @type {Bajo.TConfig}
|
|
104
114
|
* @see {@tutorial config}
|
|
105
115
|
*/
|
|
106
116
|
this.config = {}
|
|
107
117
|
|
|
108
118
|
/**
|
|
109
|
-
* Hooks container.
|
|
110
|
-
*
|
|
119
|
+
* Hooks container. This is where all hooks definition are stored.
|
|
111
120
|
* @type {Array<Bajo.THook>}
|
|
112
121
|
*/
|
|
113
122
|
this.hooks = []
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
+
* Bajo boot process. This method is called by the {@link App} class during the app's boot process:
|
|
127
|
+
* 1. Building Bajo's {@link module:Helper.buildBaseConfig|base configuration}
|
|
128
|
+
* 2. Collect all {@link module:Helper.collectConfigHandlers|config handlers} from all loaded plugins
|
|
129
|
+
* 3. Build {@link module:Helper.buildConfig|configuration} object
|
|
130
|
+
* 4. {@link module:Helper.buildPlugins|Building plugins} listed in `package.json` or `.plugins` file
|
|
131
|
+
* 5. Determining the {@link module:Helper.bootOrder|boot order}
|
|
132
|
+
* 6. Ensure the {@link module:Helper.checkNameAliases|uniqueness} of all plugins' name and alias
|
|
133
|
+
* 7. Ensure all plugins {@link module:Helper.checkDependencies|dependencies} are met
|
|
134
|
+
* 8. Collect all {@link module:Helper.collectHooks|hooks} from all loaded plugins
|
|
135
|
+
* 9. {@link module:Helper.runPlugins|Run all plugins} according to the boot order
|
|
136
|
+
* 10. And finally attaching all {@link module:Helper.exitHandler|exit handlers} it could find
|
|
126
137
|
*
|
|
127
138
|
* @method
|
|
128
139
|
* @async
|
|
129
140
|
*/
|
|
130
|
-
|
|
141
|
+
bootApp = async () => {
|
|
131
142
|
await buildBaseConfig.call(this)
|
|
132
143
|
await collectConfigHandlers.call(this)
|
|
133
|
-
await
|
|
144
|
+
await buildConfig.call(this)
|
|
134
145
|
await buildPlugins.call(this)
|
|
135
146
|
await bootOrder.call(this)
|
|
136
|
-
await
|
|
147
|
+
await checkNameAliases.call(this)
|
|
148
|
+
await checkDependencies.call(this)
|
|
149
|
+
await collectHooks.call(this)
|
|
150
|
+
await runPlugins.call(this)
|
|
137
151
|
await exitHandler.call(this)
|
|
138
152
|
if (this.app.bajoSpatial) {
|
|
139
|
-
this.anekaSpatial = await this.importPkg('bajoSpatial:aneka-spatial')
|
|
153
|
+
this.app.lib.anekaSpatial = await this.importPkg('bajoSpatial:aneka-spatial')
|
|
140
154
|
}
|
|
141
155
|
}
|
|
142
156
|
|
|
@@ -191,12 +205,24 @@ class Bajo extends Plugin {
|
|
|
191
205
|
}
|
|
192
206
|
|
|
193
207
|
/**
|
|
194
|
-
* Break name to
|
|
208
|
+
* Break `name` to their components.
|
|
209
|
+
*
|
|
210
|
+
* If path starts with `//`, e.g. name is `https://example.com`, it will be treated as a URL and returned as an
|
|
211
|
+
* object with only `path` property filled with the original name.
|
|
212
|
+
*
|
|
213
|
+
* If query string is present in the path, it will be parsed and returned as an object with `qs`
|
|
214
|
+
* property containing the parsed query string.
|
|
215
|
+
*
|
|
216
|
+
* If path contains parameters in the format of `:key|value` or `{key|value}`, they will be extracted and returned
|
|
217
|
+
* as an object with `params` property containing the extracted parameters.
|
|
218
|
+
*
|
|
219
|
+
* This method is one of the most used method in Bajo. It is because every files, names, resources, and even commands
|
|
220
|
+
* are all identified by their namespace and path.
|
|
195
221
|
*
|
|
196
222
|
* @method
|
|
197
|
-
* @param {
|
|
198
|
-
* @param {boolean} [checkNs=true] - If `true` (default), namespace
|
|
199
|
-
* @returns {TNsPathResult}
|
|
223
|
+
* @param {Bajo.TNsPathPairs} name - Name in format `ns:path`
|
|
224
|
+
* @param {boolean} [checkNs=true] - If `true` (default), check if the namespace exists in the app. If not, throw an error
|
|
225
|
+
* @returns {Bajo.TNsPathResult}
|
|
200
226
|
*/
|
|
201
227
|
breakNsPath = (name = '', checkNs = true) => {
|
|
202
228
|
let [ns, ...path] = name.split(':')
|
|
@@ -225,14 +251,17 @@ class Bajo extends Plugin {
|
|
|
225
251
|
const params = {}
|
|
226
252
|
for (const idx in parts) {
|
|
227
253
|
const part = parts[idx]
|
|
228
|
-
if (part
|
|
254
|
+
if (part.indexOf('|') === -1 && ![':', '{'].includes(part[0])) {
|
|
229
255
|
realParts.push(part)
|
|
230
256
|
continue
|
|
231
257
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
258
|
+
let [key, val] = part.split('|')
|
|
259
|
+
if (!val) val = key[0] === '{' ? '}' : ''
|
|
260
|
+
key = key.slice(1)
|
|
261
|
+
val = val[val.length - 1] === '}' ? val.slice(0, val.length - 1) : val
|
|
262
|
+
// parts[idx] = key
|
|
263
|
+
params[key] = val
|
|
264
|
+
realParts.push(isEmpty(val) ? parts[idx] : val)
|
|
236
265
|
}
|
|
237
266
|
path = parts.join('/')
|
|
238
267
|
const realPath = realParts.join('/')
|
|
@@ -240,33 +269,39 @@ class Bajo extends Plugin {
|
|
|
240
269
|
if (!isEmpty(qs)) fullPath += ('?' + querystring.stringify(qs, null, null, { encodeURIComponent: (text) => (text) }))
|
|
241
270
|
let realFullPath = realPath
|
|
242
271
|
if (!isEmpty(qs)) realFullPath += ('?' + querystring.stringify(qs, null, null, { encodeURIComponent: (text) => (text) }))
|
|
243
|
-
return { ns, path, subNs, subSubNs, qs, fullPath, fullNs, realPath, realFullPath }
|
|
272
|
+
return { ns, path, subNs, subSubNs, qs, params, fullPath, fullNs, realPath, realFullPath }
|
|
244
273
|
}
|
|
245
274
|
|
|
246
275
|
/**
|
|
247
|
-
* Method to transform config's array or object into
|
|
276
|
+
* Method to transform config's array or object into a collection of objects with uniformed structure. This is useful to build a collection of items
|
|
277
|
+
* from config's array or object, e.g. a collection of commands, routes, etc.
|
|
278
|
+
*
|
|
279
|
+
* You typically also provide a `handler` function to transform each item in the collection. The handler function will be called with an object
|
|
280
|
+
* containing the following keys:
|
|
281
|
+
* - `item`: the current item in the collection
|
|
282
|
+
* - `index`: the index of the current item in the collection
|
|
283
|
+
* - `cfg`: the entire config object
|
|
248
284
|
*
|
|
249
285
|
* @method
|
|
250
286
|
* @async
|
|
251
|
-
* @param {Object} options - Options
|
|
252
|
-
* @param {string} [options.ns] - Namespace. If not provided, defaults to `bajo`
|
|
253
|
-
* @param {function} [options.handler] -
|
|
254
|
-
* @param {string[]} [options.dupChecks=[]] - Array of keys to check for duplicates
|
|
255
|
-
* @param {string} options.container - Key used as container name
|
|
256
|
-
* @param {boolean} [options.useDefaultName=true] - If true (default) and `name` key is not provided, returned
|
|
257
|
-
* @
|
|
258
|
-
* @
|
|
259
|
-
* @
|
|
287
|
+
* @param {Object} [options={}] - Options
|
|
288
|
+
* @param {string} [options.ns='bajo'] - Namespace. If not provided, defaults to `bajo`
|
|
289
|
+
* @param {function} [options.handler] - Function to transform each item while building
|
|
290
|
+
* @param {string[]} [options.dupChecks=[]] - Array of keys to check for duplicates
|
|
291
|
+
* @param {string} options.container - Key used as container name
|
|
292
|
+
* @param {boolean} [options.useDefaultName=true] - If true (default) and `name` key is not provided, returned item will be named `default`
|
|
293
|
+
* @returns {Array<Object>} Returned collection of objects
|
|
294
|
+
* @see module:Hook.{ns}:beforeBuildCollection
|
|
295
|
+
* @see module:Hook.{ns}:afterBuildCollection
|
|
260
296
|
*/
|
|
261
297
|
buildCollections = async (options = {}) => {
|
|
262
298
|
const { parseObject } = this.app.lib
|
|
263
|
-
|
|
264
|
-
if (!ns) ns = this.ns
|
|
299
|
+
const { ns = this.ns, handler, dupChecks = [], container, useDefaultName = true, noDefault = true } = options
|
|
265
300
|
const cfg = this.app[ns].getConfig()
|
|
266
301
|
let items = get(cfg, container, [])
|
|
267
302
|
if (!isArray(items)) items = [items]
|
|
268
303
|
this.app[ns].log.trace('collecting%s', this.t(container))
|
|
269
|
-
await this.runHook(`${ns}:beforeBuildCollection`, container)
|
|
304
|
+
await this.runHook(`${ns}:beforeBuildCollection`, container, items)
|
|
270
305
|
const deleted = []
|
|
271
306
|
for (const index in items) {
|
|
272
307
|
const item = parseObject(items[index])
|
|
@@ -300,8 +335,7 @@ class Bajo extends Plugin {
|
|
|
300
335
|
}
|
|
301
336
|
|
|
302
337
|
/**
|
|
303
|
-
* Calling
|
|
304
|
-
*
|
|
338
|
+
* Calling a function handler in any plugins:
|
|
305
339
|
* - If name is a string, the corresponding plugin's method will be called with passed args as its parameters
|
|
306
340
|
* - If name is a plugin instance, this will be used as the scope instead. The first args is now the handler name and the rest are its parameters
|
|
307
341
|
* - If name is a function, this function will be run under scope with the remaining args
|
|
@@ -309,9 +343,9 @@ class Bajo extends Plugin {
|
|
|
309
343
|
*
|
|
310
344
|
* @method
|
|
311
345
|
* @async
|
|
312
|
-
* @param {(TNsPathPairs|
|
|
313
|
-
* @param {
|
|
314
|
-
* @returns {
|
|
346
|
+
* @param {(TNsPathPairs|Plugin|function|Object)} name - Method's name, plugin instance, function handler or plain object. See above for details
|
|
347
|
+
* @param {...*} [args] - One or more arguments passed as parameter to the handler
|
|
348
|
+
* @returns {Promise<*>} Returned value
|
|
315
349
|
*/
|
|
316
350
|
callHandler = async (item, ...args) => {
|
|
317
351
|
let result
|
|
@@ -356,8 +390,8 @@ class Bajo extends Plugin {
|
|
|
356
390
|
* @param {boolean} [options.useBajo=false] - If true, add `bajo` to the running plugins too.
|
|
357
391
|
* @param {string} [options.prefix=''] - Prepend glob pattern with prefix.
|
|
358
392
|
* @param {boolean} [options.noUnderscore=true] - If true (default), matched file with name starts with underscore is ignored.
|
|
359
|
-
* @param {
|
|
360
|
-
* @returns {
|
|
393
|
+
* @param {*} [options.returnItems] - If true, each value of returned handler call will be saved as an object with running plugin name as its keys.
|
|
394
|
+
* @returns {*}
|
|
361
395
|
*/
|
|
362
396
|
eachPlugins = async (handler, options = {}) => {
|
|
363
397
|
if (isString(options)) options = { glob: options }
|
|
@@ -424,7 +458,7 @@ class Bajo extends Plugin {
|
|
|
424
458
|
*
|
|
425
459
|
* @method
|
|
426
460
|
* @param {string} type - Format type. See {@link TBajoFormatType} for acceptable values.
|
|
427
|
-
* @param {
|
|
461
|
+
* @param {*} value - Value to format.
|
|
428
462
|
* @param {string} [dataType] - Value's data type. See {@link TBajoDataType} for acceptable values.
|
|
429
463
|
* @param {Object} [options={}] - Options.
|
|
430
464
|
* @param {boolean} [options.withUnit=true] - Return with its unit appended.
|
|
@@ -432,7 +466,6 @@ class Bajo extends Plugin {
|
|
|
432
466
|
* @returns {(Array|string)} Return string if `withUnit` is true. Otherwise is an array of `[value, unit, separator]`.
|
|
433
467
|
*/
|
|
434
468
|
formatByType = (type, value, dataType, options = {}) => {
|
|
435
|
-
const { defaultsDeep } = this.app.lib.aneka
|
|
436
469
|
const { format } = this.getUnitFormat(options)
|
|
437
470
|
const { withUnit = true } = options
|
|
438
471
|
const lang = options.lang ?? this.config.lang
|
|
@@ -449,7 +482,7 @@ class Bajo extends Plugin {
|
|
|
449
482
|
* Format value.
|
|
450
483
|
*
|
|
451
484
|
* @method
|
|
452
|
-
* @param {
|
|
485
|
+
* @param {*} value - Value to format.
|
|
453
486
|
* @param {string} [type] - Data type to use. See {@link TBajoDataType} for acceptable values. If not provided, return the untouched value.
|
|
454
487
|
* @param {Object} [options={}] - Options.
|
|
455
488
|
* @param {string} [options.emptyValue=''] - Empty value to use if function resulted empty. Defaults to the one from your config.
|
|
@@ -460,7 +493,6 @@ class Bajo extends Plugin {
|
|
|
460
493
|
* @returns {string} Formatted value.
|
|
461
494
|
*/
|
|
462
495
|
format = (value, type, options = {}) => {
|
|
463
|
-
const { defaultsDeep, isSet } = this.app.lib.aneka
|
|
464
496
|
const { format } = this.config.intl
|
|
465
497
|
const { emptyValue = format.emptyValue } = options
|
|
466
498
|
const lang = options.lang ?? this.config.lang
|
|
@@ -585,7 +617,6 @@ class Bajo extends Plugin {
|
|
|
585
617
|
* @returns {(Object|Array)} See above.
|
|
586
618
|
*/
|
|
587
619
|
importPkg = async (...pkgs) => {
|
|
588
|
-
const { defaultsDeep } = this.app.lib.aneka
|
|
589
620
|
const result = {}
|
|
590
621
|
const notFound = []
|
|
591
622
|
let opts = { returnDefault: true, throwNotFound: false }
|
|
@@ -600,7 +631,7 @@ class Bajo extends Plugin {
|
|
|
600
631
|
notFound.push(pkg)
|
|
601
632
|
continue
|
|
602
633
|
}
|
|
603
|
-
const p = this.
|
|
634
|
+
const p = await this.fromJson(`${dir}/package.json`, { readFromFile: true, throwNotFound: opts.throwNotFound })
|
|
604
635
|
const mainFileOrg = dir + '/' + (p.main ?? get(p, 'exports.default', 'index.js'))
|
|
605
636
|
let mainFile = resolvePath(mainFileOrg, os.platform() === 'win32')
|
|
606
637
|
if (isEmpty(path.extname(mainFile))) {
|
|
@@ -675,35 +706,34 @@ class Bajo extends Plugin {
|
|
|
675
706
|
}
|
|
676
707
|
|
|
677
708
|
/**
|
|
678
|
-
*
|
|
709
|
+
* Return human friendly joined array of items.
|
|
679
710
|
*
|
|
680
711
|
* @method
|
|
681
|
-
* @param {
|
|
682
|
-
* @param {(string|Object)} options - If provided and is a string, it will be used as separator.
|
|
712
|
+
* @param {Array<*>} input - Array to join
|
|
713
|
+
* @param {(string|Object)} [options={}] - If provided and is a string, it will be used as separator.
|
|
683
714
|
* @param {string} [options.separator=', '] - Separator to use.
|
|
684
|
-
* @param {string} [options.lastSeparator=and] - Text to use as the last separator.
|
|
715
|
+
* @param {string} [options.lastSeparator='and'] - Text to use as the last separator.
|
|
685
716
|
* @returns {string}
|
|
686
717
|
*/
|
|
687
718
|
join = (input = [], options = {}) => {
|
|
688
719
|
const array = [...input]
|
|
689
720
|
if (isString(options)) options = { separator: options }
|
|
690
|
-
|
|
721
|
+
const { separator = ', ', lastSeparator = 'and', lang } = options
|
|
691
722
|
const translate = (val) => {
|
|
692
723
|
return this.t(val, { lang }).toLowerCase()
|
|
693
724
|
}
|
|
694
725
|
if (array.length === 0) return translate('none')
|
|
695
726
|
if (array.length === 1) return array[0]
|
|
696
|
-
lastSeparator = translate(lastSeparator)
|
|
697
727
|
const last = (array.pop() ?? '').trim()
|
|
698
|
-
return array.map(a => (a + '').trim()).join(separator) + ` ${lastSeparator} ${last}`
|
|
728
|
+
return array.map(a => (a + '').trim()).join(separator) + ` ${translate(lastSeparator)} ${last}`
|
|
699
729
|
}
|
|
700
730
|
|
|
701
731
|
/**
|
|
702
|
-
*
|
|
732
|
+
* Get numeric portion and its unit from a string.
|
|
703
733
|
*
|
|
704
734
|
* @method
|
|
705
|
-
* @param {string} [value=''] - Value to get its numeric portion
|
|
706
|
-
* @param {string} [defUnit=''] - Default unit if value doesn't have one
|
|
735
|
+
* @param {string} [value=''] - Value to get its numeric portion
|
|
736
|
+
* @param {string} [defUnit=''] - Default unit if value doesn't have one
|
|
707
737
|
* @returns {string}
|
|
708
738
|
*/
|
|
709
739
|
numUnit = (value = '', defUnit = '') => {
|
|
@@ -714,27 +744,31 @@ class Bajo extends Plugin {
|
|
|
714
744
|
|
|
715
745
|
/**
|
|
716
746
|
* Read and parse file as config object. Supported types: `.js`, `.json` and `.yml/.yaml`.
|
|
717
|
-
* More supports can be added using plugin. {@link https://github.
|
|
747
|
+
* More supports can be added using plugin. {@link https://ardhi.github.io/bajo-config|bajo-config} gives you additional supports for `.yml`, `.yaml` and `.toml` file.
|
|
718
748
|
*
|
|
719
749
|
* If file extension is `.*`, it will be auto detected and parsed accordingly
|
|
720
750
|
*
|
|
721
751
|
* @method
|
|
722
752
|
* @async
|
|
723
753
|
* @param {string} file - File to read and parse.
|
|
724
|
-
* @param {Object} [options={}] - Options
|
|
725
|
-
* @param {boolean} [options.ignoreError] - Any exception will be silently discarded
|
|
726
|
-
* @param {string} [options.ns] - If
|
|
727
|
-
* @param {string} [options.
|
|
728
|
-
* @param {
|
|
729
|
-
* @param {
|
|
730
|
-
* @param {
|
|
754
|
+
* @param {Object} [options={}] - Options
|
|
755
|
+
* @param {boolean} [options.ignoreError] - Any exception will be silently discarded
|
|
756
|
+
* @param {string} [options.ns] - If provided, scoped to this namespace. Otherwise, the running plugin's namespace will be used
|
|
757
|
+
* @param {string} [options.baseNs] - If provided, it will be used as the base namespace for extending config from other plugins
|
|
758
|
+
* @param {string|string[]|boolean} [options.extend] - If provided, it will be used as the base namespace for extending config from other plugins. Set to `false` to disable extending
|
|
759
|
+
* @param {boolean} [options.checkOverride] - If `true` and `baseNs` is provided or `extend` is not `false`, check for override config in main plugin
|
|
760
|
+
* @param {boolean|string} [options.merge] - If `true`, config from other plugins will be merged into the original config. If `concat`, it performs array concatenation if object is an array. Otherwise, it will perform a deep defaults merge
|
|
761
|
+
* @param {string} [options.pattern] - If provided and auto detection is on (extension is `.*`), it will be used for instead the auto generated one
|
|
762
|
+
* @param {Object} [options.defValue={}] - Default value to use if value returned empty
|
|
763
|
+
* @param {Object} [options.parserOpts={}] - Parser options
|
|
764
|
+
* @param {Object} [options.globOpts={}] - {@link https://github.com/mrmlnc/fast-glob|fast-glob} options
|
|
731
765
|
* @returns {Object}
|
|
732
766
|
*/
|
|
733
767
|
readConfig = async (file, options = {}) => {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
768
|
+
let {
|
|
769
|
+
ns, baseNs, extend, checkOverride, merge: merged, pattern, ignoreError = true,
|
|
770
|
+
defValue = {}, parserOpts = {}, globOpts = {}, handler, cache = {}
|
|
771
|
+
} = options
|
|
738
772
|
|
|
739
773
|
const getParseOptsArgs = (opts, orig) => {
|
|
740
774
|
opts.parserOpts = opts.parserOpts ?? {}
|
|
@@ -746,6 +780,15 @@ class Bajo extends Plugin {
|
|
|
746
780
|
else opts.parserOpts.args.push({ _orig: orig })
|
|
747
781
|
}
|
|
748
782
|
|
|
783
|
+
const binder = (...args) => {
|
|
784
|
+
if (merged === 'concat') {
|
|
785
|
+
return mergeWith(...args, (objValue, srcValue) => {
|
|
786
|
+
if (isArray(objValue)) return objValue.concat(srcValue)
|
|
787
|
+
})
|
|
788
|
+
}
|
|
789
|
+
return merged ? merge(...args) : defaultsDeep(...args)
|
|
790
|
+
}
|
|
791
|
+
|
|
749
792
|
const output = async (obj) => {
|
|
750
793
|
let orig = parseObject(obj)
|
|
751
794
|
if (!baseNs || extend === false) {
|
|
@@ -755,7 +798,7 @@ class Bajo extends Plugin {
|
|
|
755
798
|
}
|
|
756
799
|
const { suffix = '', keys = [] } = options
|
|
757
800
|
let bases = this.app.getAllNs()
|
|
758
|
-
if (isString(extend)) extend = extend.split(',').map(i => i.trim)
|
|
801
|
+
if (isString(extend)) extend = extend.split(',').map(i => i.trim())
|
|
759
802
|
if (isArray(extend)) bases = [...extend, 'main']
|
|
760
803
|
bases = uniq(bases)
|
|
761
804
|
let ext = isArray(orig) ? [] : {}
|
|
@@ -776,14 +819,13 @@ class Bajo extends Plugin {
|
|
|
776
819
|
if (!isEmpty(result)) orig = result
|
|
777
820
|
}
|
|
778
821
|
getParseOptsArgs(opts, orig)
|
|
779
|
-
const binder = merged ? merge : defaultsDeep
|
|
780
822
|
for (const base of bases) {
|
|
781
823
|
if (!this.app[base]) continue
|
|
782
824
|
options.sourceNs = base
|
|
783
825
|
const fileExt = `${this.app[base].dir.pkg}/extend/${baseNs}/extend/${ns}${suffix}/${_path}`
|
|
784
|
-
await this.runHook('bajo.extend:beforeReadConfig', fileExt, options)
|
|
826
|
+
await this.runHook('bajo.extend:beforeReadConfig', fileExt, orig, options)
|
|
785
827
|
const result = parseObject(await this.readConfig(fileExt, { ...opts, extend: false, merge: false }))
|
|
786
|
-
await this.runHook('bajo.extend:afterReadConfig', fileExt, result, options)
|
|
828
|
+
await this.runHook('bajo.extend:afterReadConfig', fileExt, orig, result, options)
|
|
787
829
|
if (isEmpty(result)) continue
|
|
788
830
|
if (isArray(result)) ext = [...result, ...ext]
|
|
789
831
|
else ext = binder({}, result, ext)
|
|
@@ -800,23 +842,24 @@ class Bajo extends Plugin {
|
|
|
800
842
|
if (cache.name) result = await this.app.cache.load(cache.name, cache.ttlDur)
|
|
801
843
|
if (result) return result
|
|
802
844
|
await this.runHook('bajo:beforeReadConfig', file, options)
|
|
803
|
-
|
|
845
|
+
const readOpts = {
|
|
846
|
+
readFromFile: true,
|
|
847
|
+
throwNotFound: !ignoreError,
|
|
848
|
+
defValue,
|
|
849
|
+
parserOpts
|
|
850
|
+
}
|
|
804
851
|
if (!ns) ns = this.ns
|
|
805
852
|
file = resolvePath(this.app.getPluginFile(file))
|
|
806
853
|
let ext = path.extname(file)
|
|
807
854
|
const fname = path.dirname(file) + '/' + path.basename(file, ext)
|
|
808
855
|
ext = ext.toLowerCase()
|
|
809
|
-
if (['.js', '.json'].includes(ext)) {
|
|
810
|
-
const item = find(this.app.configHandlers, { ext })
|
|
811
|
-
return await output(await item.readHandler.call(this.app[ns], file, parserOpts))
|
|
812
|
-
}
|
|
813
856
|
if (!['', '.*'].includes(ext)) {
|
|
814
857
|
const item = find(this.app.configHandlers, { ext })
|
|
815
858
|
if (!item) {
|
|
816
859
|
if (!ignoreError) throw this.error('cantParse%s', file, { code: 'BAJO_CONFIG_NO_PARSER' })
|
|
817
860
|
return await output(defValue)
|
|
818
861
|
}
|
|
819
|
-
return await output(await item.readHandler.call(this.app[item.ns], file,
|
|
862
|
+
return await output(await item.readHandler.call(this.app[item.ns], file, readOpts))
|
|
820
863
|
}
|
|
821
864
|
const formats = this.app.getConfigFormats(true)
|
|
822
865
|
const item = pattern ?? `${fname}.{${formats.join(',')}}`
|
|
@@ -834,147 +877,174 @@ class Bajo extends Plugin {
|
|
|
834
877
|
continue
|
|
835
878
|
}
|
|
836
879
|
const _ns = ['.js', '.json'].includes(ext) ? ns : item.ns
|
|
837
|
-
config = await item.readHandler.call(this.app[_ns], f,
|
|
880
|
+
config = await item.readHandler.call(this.app[_ns], f, readOpts)
|
|
838
881
|
if (!isEmpty(config)) break
|
|
839
882
|
}
|
|
840
883
|
return await output(config)
|
|
841
884
|
}
|
|
842
885
|
|
|
843
|
-
/**
|
|
844
|
-
* Read and parse json file.
|
|
845
|
-
*
|
|
846
|
-
* @method
|
|
847
|
-
* @param {string} file - File to read.
|
|
848
|
-
* @param {boolean} [thrownNotFound=false] - If `true`, silently ignore if file is not found.
|
|
849
|
-
* @returns {Object}
|
|
850
|
-
*/
|
|
851
|
-
readJson = (file, thrownNotFound = false) => {
|
|
852
|
-
const { parseObject } = this.app.lib
|
|
853
|
-
if (isPlainObject(thrownNotFound)) thrownNotFound = false
|
|
854
|
-
if (!fs.existsSync(file) && thrownNotFound) throw this.error('notFound%s%s', this.t('file'), file)
|
|
855
|
-
let resp
|
|
856
|
-
try {
|
|
857
|
-
resp = fs.readFileSync(file, 'utf8')
|
|
858
|
-
} catch (err) {}
|
|
859
|
-
if (isEmpty(resp)) return resp
|
|
860
|
-
return parseObject(JSON.parse(resp))
|
|
861
|
-
}
|
|
862
|
-
|
|
863
886
|
/**
|
|
864
887
|
* Read and parse JavaScript file.
|
|
865
888
|
*
|
|
866
889
|
* @async
|
|
867
890
|
* @method
|
|
868
|
-
* @param {string} file - File to read and parse
|
|
869
|
-
* @param {
|
|
870
|
-
* @
|
|
891
|
+
* @param {string} file - File to read and parse
|
|
892
|
+
* @param {boolean} [options.readFromFile=false] - Ignored for this method. Always read from file.
|
|
893
|
+
* @param {boolean} [options.throwNotFound=false] - If `true`, throw exception if file is not found
|
|
894
|
+
* @param {object} [options.defValue={}] - Default value to use if value returned empty
|
|
895
|
+
* @param {object} [options.parserOpts={}] - Options to be passed if file exports a function
|
|
896
|
+
* @returns {Object} Parsed JavaScript object
|
|
871
897
|
*/
|
|
872
898
|
async fromJs (file, options = {}) {
|
|
873
|
-
|
|
899
|
+
if (isBoolean(options)) options = {}
|
|
900
|
+
options.defValue = options.defValue ?? {}
|
|
901
|
+
if (options.throwNotFound && !fs.existsSync(file)) throw this.error('notFound%s%s', this.t('file'), file)
|
|
874
902
|
let mod = await importModule(file)
|
|
875
|
-
if (isFunction(mod)) mod = await mod.call(this,
|
|
876
|
-
return mod
|
|
903
|
+
if (isFunction(mod)) mod = await mod.call(this, options.parserOpts)
|
|
904
|
+
return isEmpty(mod) ? options.defValue : mod
|
|
877
905
|
}
|
|
878
906
|
|
|
879
907
|
/**
|
|
880
|
-
*
|
|
908
|
+
* Parse JSON string or read and parse JSON file.
|
|
881
909
|
*
|
|
882
|
-
* @
|
|
883
|
-
* @
|
|
884
|
-
* @
|
|
910
|
+
* @async
|
|
911
|
+
* @method
|
|
912
|
+
* @param {string} text - Text to be parsed or file path to be read if `options.readFromFile` is `true`
|
|
913
|
+
* @param {object|boolean} [options={}] - Options object. If a boolean is provided, it will be treated as `options.readFromFile`
|
|
914
|
+
* @param {boolean} [options.readFromFile=false] - If `true`, `text` is treated as a file path
|
|
915
|
+
* @param {boolean} [options.throwNotFound=false] - If `true`, throw exception if file is not found
|
|
916
|
+
* @param {object} [options.defValue={}] - Default value to use if value returned empty
|
|
917
|
+
* @param {object} [options.parserOpts={}] - Options to be passed to `JSON.parse()`
|
|
918
|
+
* @returns {Object} Parsed object.
|
|
919
|
+
* @see Bajo#toJson
|
|
885
920
|
*/
|
|
886
|
-
fromJson (
|
|
887
|
-
|
|
888
|
-
|
|
921
|
+
async fromJson (text, options = {}) {
|
|
922
|
+
if (isBoolean(options)) options = { readFromFile: options }
|
|
923
|
+
options.defValue = options.defValue ?? {}
|
|
924
|
+
if (options.readFromFile && !fs.existsSync(text) && options.throwNotFound) throw this.error('notFound%s%s', this.t('file'), text)
|
|
925
|
+
const content = options.readFromFile ? fs.readFileSync(text, 'utf8') : text
|
|
926
|
+
const result = JSON.parse(content)
|
|
927
|
+
return isEmpty(result) ? options.defValue : result
|
|
889
928
|
}
|
|
890
929
|
|
|
891
930
|
/**
|
|
892
|
-
* Parse YAML text
|
|
931
|
+
* Parse YAML text or read and parse YAML file.
|
|
893
932
|
*
|
|
933
|
+
* @async
|
|
894
934
|
* @method
|
|
895
|
-
* @param {string} text - Text to be parsed
|
|
896
|
-
* @param {object} options - Options object
|
|
935
|
+
* @param {string} text - Text to be parsed or file path to be read if `options.readFromFile` is `true`
|
|
936
|
+
* @param {object|boolean} [options={}] - Options object. If a boolean is provided, it will be treated as `options.readFromFile`
|
|
937
|
+
* @param {boolean} [options.readFromFile=false] - If `true`, `text` is treated as a file path
|
|
938
|
+
* @param {boolean} [options.throwNotFound=false] - If `true`, throw exception if file is not found
|
|
939
|
+
* @param {object} [options.parserOpts={}] - Options to be passed to `YAML.load()`
|
|
940
|
+
* @param {object} [options.defValue={}] - Default value to use if value returned empty
|
|
897
941
|
* @returns {object} Parsed object
|
|
942
|
+
* @see Bajo#toYaml
|
|
898
943
|
*/
|
|
899
|
-
fromYaml
|
|
900
|
-
|
|
944
|
+
async fromYaml (text, options = {}) {
|
|
945
|
+
if (isBoolean(options)) options = { readFromFile: options }
|
|
946
|
+
options.defValue = options.defValue ?? {}
|
|
947
|
+
if (options.readFromFile && !fs.existsSync(text) && options.throwNotFound) throw this.error('notFound%s%s', this.t('file'), text)
|
|
901
948
|
const content = options.readFromFile ? fs.readFileSync(text, 'utf8') : text
|
|
902
|
-
|
|
949
|
+
const result = yaml.load(content)
|
|
950
|
+
return isEmpty(result) ? options.defValue : result
|
|
903
951
|
}
|
|
904
952
|
|
|
905
953
|
/**
|
|
906
|
-
* Parse YML text. Alias for fromYaml.
|
|
954
|
+
* Parse YML text or read and parse YML file. Alias for {@link Bajo#fromYaml}.
|
|
907
955
|
*
|
|
956
|
+
* @async
|
|
908
957
|
* @method
|
|
909
|
-
* @param {string} text - Text to be parsed
|
|
910
|
-
* @param {object} options - Options object
|
|
958
|
+
* @param {string} text - Text to be parsed or file path to be read if `options.readFromFile` is `true`
|
|
959
|
+
* @param {object|boolean} [options={}] - Options object. If a boolean is provided, it will be treated as `options.readFromFile`
|
|
960
|
+
* @param {boolean} [options.readFromFile=false] - If `true`, `text` is treated as a file path
|
|
961
|
+
* @param {boolean} [options.throwNotFound=false] - If `true`, throw exception if file is not found
|
|
962
|
+
* @param {object} [options.defValue={}] - Default value to use if value returned empty
|
|
963
|
+
* @param {object} [options.parserOpts={}] - Options to be passed to `YAML.load()`
|
|
911
964
|
* @returns {object} Parsed object
|
|
965
|
+
* @see Bajo#toYml
|
|
912
966
|
*/
|
|
913
|
-
fromYml
|
|
967
|
+
async fromYml (text, options = {}) {
|
|
914
968
|
return this.fromYaml(text, options)
|
|
915
969
|
}
|
|
916
970
|
|
|
917
971
|
/**
|
|
918
|
-
* Convert data to JSON string.
|
|
972
|
+
* Convert data to JSON string, optionally write to file if `options.writeToFile` is provided.
|
|
919
973
|
*
|
|
974
|
+
* @async
|
|
920
975
|
* @method
|
|
921
|
-
* @param {Object} data - Data to convert to JSON string
|
|
922
|
-
* @param {Object} [options={}] - Options.
|
|
923
|
-
* @param {
|
|
924
|
-
* @param {
|
|
925
|
-
* @returns {string} JSON string
|
|
976
|
+
* @param {Object} data - Data to convert to JSON string
|
|
977
|
+
* @param {Object|string} [options={}] - Options object. If a string is provided, it will be treated as `options.writeToFile`
|
|
978
|
+
* @param {string} [options.writeToFile] - If not empty, write result to this file path instead of returning it as string
|
|
979
|
+
* @param {Object} [options.parserOpts={}] - Options for `JSON.stringify()`
|
|
980
|
+
* @returns {Promise<string|void>} JSON string or void if written to file
|
|
981
|
+
* @see Bajo#fromJson
|
|
926
982
|
*/
|
|
927
|
-
toJson
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
983
|
+
async toJson (data, options = {}) {
|
|
984
|
+
if (isString(options)) options = { writeToFile: options }
|
|
985
|
+
options.parserOpts = options.parserOpts ?? {}
|
|
986
|
+
options.parserOpts.space = options.parserOpts.space ?? 2
|
|
987
|
+
const content = JSON.stringify(data, null, options.parserOpts)
|
|
988
|
+
if (isString(options.writeToFile)) {
|
|
989
|
+
fs.writeFileSync(options.writeToFile, content, 'utf8')
|
|
931
990
|
return
|
|
932
991
|
}
|
|
933
992
|
return content
|
|
934
993
|
}
|
|
935
994
|
|
|
936
995
|
/**
|
|
937
|
-
* Convert object to YAML string
|
|
996
|
+
* Convert object to YAML string, optionally write to file if `options.writeToFile` is provided.
|
|
938
997
|
*
|
|
998
|
+
* @async
|
|
939
999
|
* @method
|
|
940
1000
|
* @param {object} object - Object to be converted
|
|
941
|
-
* @param {object} options - Options object
|
|
942
|
-
* @
|
|
1001
|
+
* @param {object|string} [options] - Options object. If a string is provided, it will be treated as `options.writeToFile`
|
|
1002
|
+
* @param {string} [options.writeToFile] - If not empty, write result to this file path instead of returning it as string
|
|
1003
|
+
* @param {Object} [options.parserOpts={}] - Options for `YAML.dump()`
|
|
1004
|
+
* @returns {Promise<string|void>} YAML string or void if written to file
|
|
1005
|
+
* @see Bajo#fromYaml
|
|
943
1006
|
*/
|
|
944
|
-
toYaml
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
const content = yaml.dump(object,
|
|
1007
|
+
async toYaml (object, options = {}) {
|
|
1008
|
+
if (isString(options)) options = { writeToFile: options }
|
|
1009
|
+
options.parserOpts = options.parserOpts ?? {}
|
|
1010
|
+
const content = yaml.dump(object, options.parserOpts)
|
|
948
1011
|
if (options.writeToFile) {
|
|
949
|
-
|
|
950
|
-
|
|
1012
|
+
if (isString(options.writeToFile)) {
|
|
1013
|
+
fs.writeFileSync(options.writeToFile, content, 'utf8')
|
|
1014
|
+
return
|
|
1015
|
+
}
|
|
951
1016
|
}
|
|
952
1017
|
return content
|
|
953
1018
|
}
|
|
954
1019
|
|
|
955
1020
|
/**
|
|
956
|
-
* Convert object to YML string. Alias for toYaml.
|
|
957
|
-
*
|
|
1021
|
+
* Convert object to YML string, optionally write to file if `options.writeToFile` is provided. Alias for {@link Bajo#toYaml}.
|
|
1022
|
+
* @async
|
|
958
1023
|
* @method
|
|
959
1024
|
* @param {object} object - Object to be converted
|
|
960
|
-
* @param {object} options - Options object
|
|
961
|
-
* @
|
|
1025
|
+
* @param {object|string} [options] - Options object. If a string is provided, it will be treated as `options.writeToFile`
|
|
1026
|
+
* @param {string} [options.writeToFile] - If not empty, write result to this file path instead of returning it as string
|
|
1027
|
+
* @param {Object} [options.parserOpts={}] - Options for `YAML.dump()`
|
|
1028
|
+
* @returns {Promise<string|void>} YAML string or void if written to file
|
|
1029
|
+
* @see Bajo#fromYml
|
|
962
1030
|
*/
|
|
963
|
-
toYml
|
|
1031
|
+
async toYml (object, options = {}) {
|
|
964
1032
|
return this.toYaml(object, options)
|
|
965
1033
|
}
|
|
966
1034
|
|
|
967
1035
|
/**
|
|
968
|
-
* Read all
|
|
1036
|
+
* Read all forms of configuration files from file path.
|
|
1037
|
+
*
|
|
1038
|
+
* Internally, it will call {@link Bajo#readConfig} twice, first for the default config file and
|
|
1039
|
+
* second for the environment based config file. Then it will merge both results using `defaultsDeep`.
|
|
969
1040
|
*
|
|
970
1041
|
* @method
|
|
971
1042
|
* @async
|
|
972
1043
|
* @param {string} path - Base path to start looking config files.
|
|
973
|
-
* @param {Object} [options={}] - Options.
|
|
1044
|
+
* @param {Object} [options={}] - Options. See {@link Bajo#readConfig} for more.
|
|
974
1045
|
* @returns {Object} Merged configuration object.
|
|
975
1046
|
*/
|
|
976
1047
|
readAllConfigs = async (path, options) => {
|
|
977
|
-
const { defaultsDeep } = this.app.lib.aneka
|
|
978
1048
|
let cfg = {}
|
|
979
1049
|
let ext = {}
|
|
980
1050
|
// default config file
|
|
@@ -993,7 +1063,14 @@ class Bajo extends Plugin {
|
|
|
993
1063
|
}
|
|
994
1064
|
|
|
995
1065
|
/**
|
|
996
|
-
* Run named
|
|
1066
|
+
* Run named {@link module:Hook}.
|
|
1067
|
+
*
|
|
1068
|
+
* If no hook found with the given name, it will return an empty array.
|
|
1069
|
+
* If a hook has `noWait` set to `true`, it will not wait for the hook to finish and will not return its result.
|
|
1070
|
+
*
|
|
1071
|
+
* > **Note**: Even though this method returns a result, it is not recommended to use the result for any purpose.
|
|
1072
|
+
* Use the result only for debugging or logging purposes. Hooks are designed to be fast, lightweight and mutate
|
|
1073
|
+
* arguments given to them so that the next hook can benefit from the changes.
|
|
997
1074
|
*
|
|
998
1075
|
* @method
|
|
999
1076
|
* @async
|
|
@@ -1023,6 +1100,7 @@ class Bajo extends Plugin {
|
|
|
1023
1100
|
|
|
1024
1101
|
/**
|
|
1025
1102
|
* Get download directory. If doesn't exist, it will be created automatically.
|
|
1103
|
+
*
|
|
1026
1104
|
* @method
|
|
1027
1105
|
* @returns {string} Absolute path to the download directory
|
|
1028
1106
|
*/
|
|
@@ -1047,25 +1125,27 @@ class Bajo extends Plugin {
|
|
|
1047
1125
|
* @returns {string} Full file path.
|
|
1048
1126
|
*/
|
|
1049
1127
|
saveAsDownload = async (file, item, printSaved = true) => {
|
|
1050
|
-
const { print } = this.app.bajo
|
|
1051
1128
|
const fname = increment(`${this.getDownloadDir()}/${trim(file, '/')}`, { fs: true })
|
|
1052
1129
|
await fs.writeFile(fname, item, 'utf8')
|
|
1053
|
-
if (printSaved) print.succeed('savedAs%s', path.resolve(fname), { skipSilence: true })
|
|
1130
|
+
if (printSaved) this.print.succeed('savedAs%s', path.resolve(fname), { skipSilence: true })
|
|
1054
1131
|
return fname
|
|
1055
1132
|
}
|
|
1056
1133
|
|
|
1057
1134
|
/**
|
|
1058
|
-
*
|
|
1135
|
+
* Parse `input` using all registered config handlers. The first handler that returns a
|
|
1059
1136
|
* valid object or array will be used.
|
|
1060
1137
|
*
|
|
1138
|
+
* Use this method if you want to parse a text `input` that can be in any format supported by the registered config handlers.
|
|
1139
|
+
*
|
|
1061
1140
|
* @method
|
|
1062
1141
|
* @param {string} input - The input string to be processed by the config handlers.
|
|
1063
1142
|
* @param {string[]} [exts] - Optional array of extensions to filter the config handlers. If provided, only handlers with matching extensions will be used.
|
|
1064
1143
|
* @param {object} [options={}] - Options to be passed to the config handlers.
|
|
1065
1144
|
* @returns {Object|Array|null} The result from the first successful config handler, or null if none succeed.
|
|
1066
1145
|
*/
|
|
1067
|
-
|
|
1146
|
+
parseWithConfig = async (input, exts, options = {}) => {
|
|
1068
1147
|
let result
|
|
1148
|
+
options.readFromFile = false
|
|
1069
1149
|
const handlers = exts ? this.app.configHandlers.filter(h => exts.includes(h.ext)) : this.app.configHandlers
|
|
1070
1150
|
for (const handler of handlers) {
|
|
1071
1151
|
if (result) break
|