bajo 0.0.1 → 0.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 (107) hide show
  1. package/.jsdoc.conf.json +38 -0
  2. package/README.md +57 -3
  3. package/boot/attach-helper.js +26 -0
  4. package/boot/boot-order.js +34 -0
  5. package/boot/build-config.js +94 -0
  6. package/boot/create-scope.js +36 -0
  7. package/boot/exit-handler.js +60 -0
  8. package/boot/helper/build-collections.js +40 -0
  9. package/boot/helper/call-helper-or-handler.js +15 -0
  10. package/boot/helper/current-loc.js +11 -0
  11. package/boot/helper/defaults-deep.js +14 -0
  12. package/boot/helper/dump.js +10 -0
  13. package/boot/helper/each-plugins.js +94 -0
  14. package/boot/helper/envs.js +14 -0
  15. package/boot/helper/error.js +47 -0
  16. package/boot/helper/fatal.js +12 -0
  17. package/boot/helper/generate-id.js +19 -0
  18. package/boot/helper/get-config.js +18 -0
  19. package/boot/helper/get-global-module-dir.js +27 -0
  20. package/boot/helper/get-helper.js +11 -0
  21. package/boot/helper/get-item-by-name.js +26 -0
  22. package/boot/helper/get-key-by-value.js +5 -0
  23. package/boot/helper/get-module-dir.js +22 -0
  24. package/boot/helper/get-plugin-name.js +39 -0
  25. package/boot/helper/get-plugin.js +7 -0
  26. package/boot/helper/import-module.js +25 -0
  27. package/boot/helper/import-pkg.js +67 -0
  28. package/boot/helper/index.js +36 -0
  29. package/boot/helper/is-empty-dir.js +9 -0
  30. package/boot/helper/is-log-in-range.js +10 -0
  31. package/boot/helper/is-set.js +5 -0
  32. package/boot/helper/is-valid-app.js +12 -0
  33. package/boot/helper/is-valid-plugin.js +12 -0
  34. package/boot/helper/log-levels.js +19 -0
  35. package/boot/helper/pascal-case.js +7 -0
  36. package/boot/helper/print.js +89 -0
  37. package/boot/helper/read-config.js +46 -0
  38. package/boot/helper/read-json.js +10 -0
  39. package/boot/helper/resolve-path.js +15 -0
  40. package/boot/helper/resolve-tpl-path.js +21 -0
  41. package/boot/helper/run-hook.js +46 -0
  42. package/boot/helper/save-as-download.js +18 -0
  43. package/boot/helper/white-space.js +3 -0
  44. package/boot/index.js +60 -0
  45. package/boot/lib/bora.js +97 -0
  46. package/boot/lib/build-helper.js +58 -0
  47. package/boot/lib/logger.js +75 -0
  48. package/boot/lib/omitted-plugin-keys.js +3 -0
  49. package/boot/lib/parse-args-argv.js +75 -0
  50. package/boot/lib/parse-env.js +36 -0
  51. package/boot/lib/shim.js +14 -0
  52. package/boot/plugins/attach-helper.js +20 -0
  53. package/boot/plugins/build-config.js +75 -0
  54. package/boot/plugins/check-clash.js +18 -0
  55. package/boot/plugins/check-dependency.js +37 -0
  56. package/boot/plugins/collect-config-handlers.js +25 -0
  57. package/boot/plugins/collect-exit-handlers.js +23 -0
  58. package/boot/plugins/collect-hooks.js +33 -0
  59. package/boot/plugins/extend-config.js +21 -0
  60. package/boot/plugins/index.js +28 -0
  61. package/boot/plugins/run.js +31 -0
  62. package/boot/run-tool.js +34 -0
  63. package/docs/boot_build-config.js.html +75 -0
  64. package/docs/boot_create-scope.js.html +25 -0
  65. package/docs/boot_index.js.html +43 -0
  66. package/docs/data/search.json +1 -0
  67. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  68. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  69. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  70. package/docs/helper_emit.js.html +18 -0
  71. package/docs/helper_envs.js.html +16 -0
  72. package/docs/helper_error.js.html +25 -0
  73. package/docs/helper_get-bajo.js.html +42 -0
  74. package/docs/helper_index.js.html +39 -0
  75. package/docs/helper_log-levels.js.html +20 -0
  76. package/docs/helper_set-hook.js.html +38 -0
  77. package/docs/helper_walk-bajos.js.html +51 -0
  78. package/docs/index.html +16 -0
  79. package/docs/module-boot.html +3 -0
  80. package/docs/module-boot_buildConfig.html +3 -0
  81. package/docs/module-boot_createScope.html +3 -0
  82. package/docs/module-helper.html +7 -0
  83. package/docs/module-helper_setHook.html +4 -0
  84. package/docs/module-helper_walkBajos.html +6 -0
  85. package/docs/scripts/core.js +655 -0
  86. package/docs/scripts/core.min.js +23 -0
  87. package/docs/scripts/resize.js +90 -0
  88. package/docs/scripts/search.js +265 -0
  89. package/docs/scripts/search.min.js +6 -0
  90. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  91. package/docs/scripts/third-party/fuse.js +9 -0
  92. package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
  93. package/docs/scripts/third-party/hljs-line-num.js +1 -0
  94. package/docs/scripts/third-party/hljs-original.js +5171 -0
  95. package/docs/scripts/third-party/hljs.js +1 -0
  96. package/docs/scripts/third-party/popper.js +5 -0
  97. package/docs/scripts/third-party/tippy.js +1 -0
  98. package/docs/scripts/third-party/tocbot.js +672 -0
  99. package/docs/scripts/third-party/tocbot.min.js +1 -0
  100. package/docs/styles/clean-jsdoc-theme-base.css +975 -0
  101. package/docs/styles/clean-jsdoc-theme-dark.css +407 -0
  102. package/docs/styles/clean-jsdoc-theme-light.css +388 -0
  103. package/docs/styles/clean-jsdoc-theme.min.css +1 -0
  104. package/package.json +36 -4
  105. package/test/helper-error.js +25 -0
  106. package/test/helper-isSet.js +41 -0
  107. package/test/helper-pathResolve.js +28 -0
package/boot/index.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Boot process:
3
+ * 1. [Creating scope]{@link module:boot/createScope}
4
+ * 2. [Building config object]{@link module:boot/buildConfig}
5
+ * 3. Attaching helpers
6
+ * 4. Attaching system report
7
+ * 5. Determine boot orders
8
+ * 6. Register plugins
9
+ * 7. Attaching exit handlers
10
+ * @module boot
11
+ */
12
+
13
+ import createScope from './create-scope.js'
14
+ import buildConfig from './build-config.js'
15
+ import attachHelper from './attach-helper.js'
16
+ import bootOrder from './boot-order.js'
17
+ import bootPlugins from './plugins/index.js'
18
+ import exitHandler from './exit-handler.js'
19
+ import runTool from './run-tool.js'
20
+ import shim from './lib/shim.js'
21
+ import { last } from 'lodash-es'
22
+ import path from 'path'
23
+ import resolvePath from './helper/resolve-path.js'
24
+
25
+ shim()
26
+
27
+ /**
28
+ * The entry point to boot Bajo based application
29
+ *
30
+ * @instance
31
+ * @async
32
+ * @returns {Object} scope
33
+ */
34
+
35
+ async function boot (cwd) {
36
+ if (!cwd) cwd = path.dirname(process.argv[1])
37
+ const l = last(process.argv)
38
+ if (l.startsWith('--cwd')) {
39
+ const parts = l.split('=')
40
+ cwd = parts[1]
41
+ }
42
+ cwd = resolvePath(cwd)
43
+ process.env.BAJOCWD = cwd
44
+ const scope = createScope()
45
+ await buildConfig.call(scope, cwd)
46
+ await attachHelper.call(scope)
47
+ await bootOrder.call(scope)
48
+ await bootPlugins.call(scope)
49
+ await exitHandler.call(scope)
50
+ // boot complete
51
+ const { runHook, log } = scope.bajo.helper
52
+ await runHook('bajo:bootComplete')
53
+ const elapsed = (new Date() - scope.bajo.runAt).toLocaleString()
54
+ log.info('Boot process completed in %sms', elapsed)
55
+ // run tool
56
+ await runTool.call(scope)
57
+ return scope
58
+ }
59
+
60
+ export default boot
@@ -0,0 +1,97 @@
1
+ import Sprintf from 'sprintf-js'
2
+ import ora from 'ora'
3
+ import { last, isPlainObject, get, isString } from 'lodash-es'
4
+
5
+ const { sprintf } = Sprintf
6
+
7
+ class Bora {
8
+ constructor (ns, ...args) {
9
+ this.ns = ns
10
+ const l = last(args)
11
+ let opts = {}
12
+ if (isPlainObject(l)) opts = args.pop()
13
+ this.opts = opts
14
+ this.ora = ora(this.opts)
15
+ this.args = args
16
+ }
17
+
18
+ setScope (scope) {
19
+ this.scope = scope
20
+ const { getConfig } = this.scope.bajo.helper
21
+ const config = getConfig()
22
+ let silent = !!config.silent
23
+ if (this.opts.skipSilent) silent = false
24
+ this.ora.isSilent = silent
25
+ const [text, ...params] = this.args
26
+ this.setText(text, ...params)
27
+ }
28
+
29
+ setText (text, ...args) {
30
+ if (isString(text)) {
31
+ const i18n = get(this, 'scope.bajoI18N.instance')
32
+ if (i18n) {
33
+ if (isPlainObject(args[0])) text = i18n.t(text, args[0])
34
+ else text = i18n.t(text, { ns: this.ns, postProcess: 'sprintf', sprintf: args })
35
+ } else text = sprintf(text, ...args)
36
+ this.ora.text = text
37
+ }
38
+ return this
39
+ }
40
+
41
+ start (text, ...args) {
42
+ this.setText(text, ...args)
43
+ this.ora.start()
44
+ return this
45
+ }
46
+
47
+ stop () {
48
+ this.ora.stop()
49
+ return this
50
+ }
51
+
52
+ succeed (text, ...args) {
53
+ this.setText(text, ...args)
54
+ this.ora.succeed()
55
+ return this
56
+ }
57
+
58
+ fail (text, ...args) {
59
+ this.setText(text, ...args)
60
+ this.ora.fail()
61
+ return this
62
+ }
63
+
64
+ warn (text, ...args) {
65
+ this.setText(text, ...args)
66
+ this.ora.warn()
67
+ return this
68
+ }
69
+
70
+ info (text, ...args) {
71
+ this.setText(text, ...args)
72
+ this.ora.info()
73
+ return this
74
+ }
75
+
76
+ clear () {
77
+ this.ora.clear()
78
+ return this
79
+ }
80
+
81
+ render () {
82
+ this.ora.render()
83
+ return this
84
+ }
85
+
86
+ fatal (text, ...args) {
87
+ this.setText(text, ...args)
88
+ this.ora.fail()
89
+ process.exit(1)
90
+ }
91
+ }
92
+
93
+ export default function (ns, ...args) {
94
+ const bora = new Bora(ns, ...args)
95
+ bora.setScope(this)
96
+ return bora
97
+ }
@@ -0,0 +1,58 @@
1
+ import fastGlob from 'fast-glob'
2
+ import { map, without, camelCase, isFunction, isPlainObject, forOwn } from 'lodash-es'
3
+ import resolvePath from '../helper/resolve-path.js'
4
+ import importModule from '../helper/import-module.js'
5
+
6
+ function stackInfo (name, ...args) {
7
+ const { log, callsites } = this.bajo.helper
8
+ const config = this.bajo.config
9
+ if (config.env === 'prod') return
10
+ if (!config.log.report.includes(`helper:${name}`)) return
11
+ const info = callsites()[2]
12
+ const file = info.getFileName()
13
+ const line = info.getLineNumber()
14
+ log.trace({ line, file, args }, 'Call helper: %s()', name)
15
+ }
16
+
17
+ const wrapFn = function (name, handler, bind) {
18
+ return (...args) => {
19
+ stackInfo.call(this, name, ...args)
20
+ if (bind) return handler.call(this, ...args)
21
+ return handler(...args)
22
+ }
23
+ }
24
+
25
+ const wrapAsyncFn = function (name, handler, bind) {
26
+ return async (...args) => {
27
+ stackInfo.call(this, name, ...args)
28
+ if (bind) return await handler.call(this, ...args)
29
+ return await handler(...args)
30
+ }
31
+ }
32
+
33
+ export default async function (dir, { pkg = 'bajo', exclude = [] } = {}) {
34
+ dir = resolvePath(dir)
35
+ exclude = map(exclude, e => `${dir}/${e}`)
36
+ let files = await fastGlob(`${dir}/**/*.js`)
37
+ files = without(files, ...exclude)
38
+ const helper = {}
39
+ for (const f of files) {
40
+ const base = f.replace(dir, '').replace('.js', '')
41
+ const name = camelCase(base)
42
+ const fnName = pkg + '.' + name
43
+ let mod = await importModule(f)
44
+ if (isFunction(mod)) {
45
+ if (mod.constructor.name === 'AsyncFunction') mod = wrapAsyncFn.call(this, fnName, mod, true)
46
+ else mod = wrapFn.call(this, fnName, mod, true)
47
+ } else if (isPlainObject(mod)) {
48
+ if (isFunction(mod.class)) mod = new mod.class(this)
49
+ else {
50
+ forOwn(mod, (v, k) => {
51
+ if (isFunction(v)) mod[k] = v.bind(this)
52
+ })
53
+ }
54
+ }
55
+ helper[name] = mod
56
+ }
57
+ return helper
58
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * test test
3
+ *
4
+ * @kind function
5
+ * @name logger
6
+ * @returns {Object}
7
+ *
8
+ */
9
+
10
+ import print from '../helper/print.js'
11
+ import os from 'os'
12
+ import { keys, each, isEmpty, without, merge, upperFirst, isString } from 'lodash-es'
13
+ // import pretty from 'prettyjson'
14
+ import getPluginName from '../helper/get-plugin-name.js'
15
+ import levels from '../helper/log-levels.js'
16
+ import isLogInRange from '../helper/is-log-in-range.js'
17
+ import dayjs from 'dayjs'
18
+
19
+ const levelList = keys(levels)
20
+
21
+ /*
22
+ const prettyOpts = {
23
+ noAlign: true,
24
+ defaultIndentation: 2,
25
+ renderUndefined: true
26
+ }
27
+ */
28
+
29
+ export default function logger () {
30
+ const config = this.bajo.config
31
+ // const format = config.log.dateFormat
32
+ const format = 'YYYY-MM-DDTHH:MM:ss.SSS[Z]'
33
+ const log = {}
34
+ const self = this
35
+ log.child = () => {
36
+ if (!config.log.logger) config.log.logger = 'bajoLogger'
37
+ if (self[config.log.logger] && self[config.log.logger].logger) return self[config.log.logger].logger.child()
38
+ return self
39
+ }
40
+ each(levelList, l => {
41
+ log[l] = (...params) => {
42
+ const config = this.bajo.config
43
+ if (config.log.level === 'silent') return
44
+ if (!isLogInRange.call(this, l)) return
45
+ let [data, msg, ...args] = params
46
+ if (isString(data)) {
47
+ args.unshift(msg)
48
+ msg = data
49
+ data = null
50
+ }
51
+ args = without(args, undefined)
52
+ const pkg = getPluginName.call(this)
53
+ msg = print._format(pkg, `[%s] ${msg}`, pkg, ...args)
54
+ const bajoLog = config.log.logger ?? 'bajoLogger'
55
+ if (this[bajoLog] && this[bajoLog].logger) {
56
+ this[bajoLog].logger[l](data, msg, ...args)
57
+ } else {
58
+ let text
59
+ const dt = new Date()
60
+ if (config.env === 'prod') {
61
+ const json = { level: levels[l], time: dt.valueOf(), pid: process.pid, hostname: os.hostname() }
62
+ if (!isEmpty(data)) merge(json, data)
63
+ merge(json, { msg })
64
+ text = JSON.stringify(json)
65
+ } else {
66
+ text = `[${dayjs(dt).utc(true).format(format)}] ${upperFirst(l)}: ${msg}`
67
+ // if (!isEmpty(data)) text += '\n ' + (pretty.render(data, prettyOpts).split('\n').join('\n '))
68
+ if (!isEmpty(data)) text += '\n' + JSON.stringify(data)
69
+ }
70
+ console.log(text)
71
+ }
72
+ }
73
+ })
74
+ return log
75
+ }
@@ -0,0 +1,3 @@
1
+ const omittedPluginKeys = ['name', 'dir', 'alias', 'pkg', 'dependencies']
2
+
3
+ export default omittedPluginKeys
@@ -0,0 +1,75 @@
1
+ import yargs from 'yargs'
2
+ import { Parser } from 'yargs/helpers'
3
+ import flat from 'flat'
4
+ import isSet from '../helper/is-set.js'
5
+ import dotenvParseVariables from 'dotenv-parse-variables'
6
+ import importModule from '../helper/import-module.js'
7
+ import { find, each, set, camelCase, forOwn } from 'lodash-es'
8
+ import fs from 'fs-extra'
9
+ import currentLoc from '../helper/current-loc.js'
10
+
11
+ const { unflatten } = flat
12
+
13
+ const parseItem = (data, delimiter) => {
14
+ return unflatten(data, {
15
+ delimiter,
16
+ safe: true,
17
+ overwrite: true,
18
+ })
19
+ }
20
+
21
+ const parseWithParser = async () => {
22
+ return Parser(process.argv.slice(2), {
23
+ configuration: {
24
+ 'camel-case-expansion': false
25
+ }
26
+ })
27
+ }
28
+
29
+ const parseWithYargs = async () => {
30
+ const parser = './app/bajo/argv-parser.js'
31
+ if (fs.existsSync(parser)) {
32
+ const mod = await importModule(parser)
33
+ return await mod(yargs)
34
+ }
35
+ const pkg = fs.readJSONSync(`${currentLoc(import.meta).dir}/../../package.json`)
36
+ let name = `node ${pkg.main}`
37
+ if (pkg.bin) name = path.basename(pkg.bin, '.js')
38
+ const cli = yargs(process.argv.slice(2))
39
+ .usage('Usage: $0 [args...]')
40
+ .scriptName(name)
41
+ .positional('args', {
42
+ describe: 'Optional one or more arguments'
43
+ })
44
+ .parserConfiguration({
45
+ 'camel-case-expansion': false
46
+ })
47
+ .version().alias('version', 'v')
48
+ .help().alias('help', 'h')
49
+ .alias('tool', 't')
50
+ if (pkg.homepage) cli.epilog(`For more information please visit ${pkg.homepage}`)
51
+ return cli.argv
52
+ }
53
+
54
+ async function parseArgsArgv ({ delimiter = '-', splitter = '--', useParser } = {}) {
55
+ if (!isSet(useParser)) useParser = find(process.argv, a => a.startsWith('--spawn'))
56
+ let argv = useParser ? await parseWithParser() : await parseWithYargs()
57
+ const args = argv._
58
+ delete argv._
59
+ delete argv.$0
60
+ argv = dotenvParseVariables(argv)
61
+
62
+ const all = { root: {} }
63
+ each(argv, (v, k) => {
64
+ const parts = k.split(splitter)
65
+ if (!parts[1]) all.root[parts[0]] = v
66
+ else set(all, `${camelCase(parts[0])}.${parts[1]}`, v)
67
+ })
68
+ const result = {}
69
+ forOwn(all, (v, k) => {
70
+ result[k] = parseItem(v, delimiter)
71
+ })
72
+ return { args, argv: result }
73
+ }
74
+
75
+ export default parseArgsArgv
@@ -0,0 +1,36 @@
1
+ import dotenvParseVariables from 'dotenv-parse-variables'
2
+ import flat from 'flat'
3
+ import dotEnv from 'dotenv'
4
+ import { each, set, camelCase, forOwn } from 'lodash-es'
5
+ const { unflatten } = flat
6
+
7
+ const parse = (data, delimiter) => {
8
+ return unflatten(data, {
9
+ delimiter,
10
+ safe: true,
11
+ overwrite: true,
12
+ transformKey: k => k.toLowerCase()
13
+ })
14
+ }
15
+
16
+ export default function ({ delimiter = '_', splitter = '__' } = {}) {
17
+ let env
18
+ try {
19
+ env = dotEnv.config()
20
+ if (env.error) throw env.error
21
+ } catch (err) {
22
+ env = { parsed: {} }
23
+ }
24
+ env = dotenvParseVariables(env.parsed, { assignToProcessEnv: false })
25
+ const all = { root: {} }
26
+ each(env, (v, k) => {
27
+ const parts = k.split(splitter)
28
+ if (!parts[1]) all.root[parts[0]] = v
29
+ else set(all, `${camelCase(parts[0])}.${parts[1]}`, v)
30
+ })
31
+ const result = {}
32
+ forOwn(all, (v, k) => {
33
+ result[k] = parse(v, delimiter)
34
+ })
35
+ return result
36
+ }
@@ -0,0 +1,14 @@
1
+ // taken from: https://vanillajstoolkit.com/polyfills/stringreplaceall/
2
+
3
+ function shim () {
4
+ if (!String.prototype.replaceAll) {
5
+ String.prototype.replaceAll = function(str, newStr){
6
+ if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
7
+ return this.replace(str, newStr)
8
+ }
9
+ return this.replace(new RegExp(str, 'g'), newStr)
10
+ }
11
+ }
12
+ }
13
+
14
+ export default shim
@@ -0,0 +1,20 @@
1
+ import buildHelper from '../lib/build-helper.js'
2
+ import { keys } from 'lodash-es'
3
+
4
+ async function runner (plugin, pkg) {
5
+ const { log, freeze } = this.bajo.helper
6
+ const dir = pkg === 'app' ? (this.bajo.config.dir.base + '/app') : this.bajo.helper.getModuleDir(pkg)
7
+ this[plugin].helper = await buildHelper.call(this, `${dir}/bajo/helper`, { pkg })
8
+ freeze(this[plugin].helper, true)
9
+ log.trace('Attach helper: %s (%d)', plugin, keys(this[plugin].helper).length)
10
+ }
11
+
12
+ async function attachHelper () {
13
+ const { log, eachPlugins } = this.bajo.helper
14
+ log.debug('Attach helpers')
15
+ await eachPlugins(async function ({ plugin, pkg }) {
16
+ await runner.call(this, plugin, pkg)
17
+ })
18
+ }
19
+
20
+ export default attachHelper
@@ -0,0 +1,75 @@
1
+ import { camelCase, pick, isString, omit, pull, each } from 'lodash-es'
2
+ import fs from 'fs-extra'
3
+ import lockfile from 'proper-lockfile'
4
+ import omittedPluginKeys from '../lib/omitted-plugin-keys.js'
5
+
6
+ export async function readAllConfigs (base, name) {
7
+ const { readConfig, getConfig } = this.bajo.helper
8
+ const config = getConfig()
9
+ let cfg = {}
10
+ try {
11
+ cfg = await readConfig(`${base}-${config.env}.*`)
12
+ } catch (err) {
13
+ if (['BAJO_CONFIG_NO_PARSER'].includes(err.code)) throw err
14
+ if (['BAJO_CONFIG_FILE_NOT_FOUND'].includes(err.code)) {
15
+ try {
16
+ cfg = await readConfig(`${base}.*`)
17
+ } catch (err) {
18
+ if (!['BAJO_CONFIG_FILE_NOT_FOUND'].includes(err.code)) throw err
19
+ }
20
+ }
21
+ }
22
+ cfg.name = name
23
+ return cfg
24
+ }
25
+
26
+ async function runner (pkg, { singles, argv, env }) {
27
+ const { log, getConfig, getModuleDir, readConfig, error, readJson, defaultsDeep } = this.bajo.helper
28
+ const config = getConfig()
29
+ const name = camelCase(pkg)
30
+ log.trace('Read configuration: %s', name)
31
+ const dir = pkg === 'app' ? (config.dir.base + '/app') : getModuleDir(pkg)
32
+ if (pkg !== 'app' && !fs.existsSync(`${dir}/bajo`)) throw error('Package \'%s\' isn\'t a valid Bajo package', pkg, { code: 'BAJO_INVALID_PACKAGE' })
33
+ let cfg = await readAllConfigs.call(this, `${dir}/bajo/config`, name)
34
+ cfg.dir = dir
35
+ const pkgJson = await readJson(`${dir + (pkg === 'app' ? '/..' : '')}/package.json`)
36
+ cfg.pkg = pick(pkgJson,
37
+ ['name', 'version', 'description', 'author', 'license', 'homepage'])
38
+ if (cfg.name === 'app') cfg.alias = 'app'
39
+ else if (!isString(cfg.alias)) cfg.alias = pkg.slice(0, 5) === 'bajo-' ? pkg.slice(5).toLowerCase() : pkg // fix. can't be overriden
40
+ // merge with config from datadir
41
+ try {
42
+ const altCfg = await readConfig(`${config.dir.data}/config/${cfg.name}.*`)
43
+ cfg = defaultsDeep({}, omit(altCfg, omittedPluginKeys), cfg)
44
+ } catch (err) {}
45
+ const envArgv = defaultsDeep({}, omit(env[cfg.name] ?? {}, omittedPluginKeys) ?? {}, omit(argv[cfg.name] ?? {}, omittedPluginKeys) ?? {})
46
+ cfg = defaultsDeep({}, envArgv ?? {}, cfg ?? {})
47
+ cfg.dependencies = cfg.dependencies ?? []
48
+ if (isString(cfg.dependencies)) cfg.dependencies = [cfg.dependencies]
49
+ if (cfg.single) {
50
+ const lockfileDir = `${config.dir.tmp}/lock`
51
+ const lockfilePath = `${lockfileDir}/${name}.lock`
52
+ fs.ensureDirSync(lockfileDir)
53
+ const file = `${cfg.dir}/package.json`
54
+ try {
55
+ await lockfile.lock(file, { lockfilePath })
56
+ } catch (err) {
57
+ singles.push(pkg)
58
+ }
59
+ }
60
+ if (!this[name]) this[name] = {}
61
+ this[name].config = cfg
62
+ }
63
+
64
+ async function buildConfig ({ singles, argv, env }) {
65
+ const { log, freeze } = this.bajo.helper
66
+ log.debug('Read configurations')
67
+ for (const pkg of this.bajo.config.plugins) {
68
+ await runner.call(this, pkg, { singles, argv, env })
69
+ }
70
+ pull(this.bajo.config.plugins, ...singles)
71
+ each(singles, s => delete this[camelCase(s)])
72
+ freeze(this.bajo.config)
73
+ }
74
+
75
+ export default buildConfig
@@ -0,0 +1,18 @@
1
+ import { find } from 'lodash-es'
2
+ import error from '../helper/error.js'
3
+
4
+ async function checkAlias () {
5
+ const { log, eachPlugins } = this.bajo.helper
6
+ log.debug('Checking alias & name clashes')
7
+ const refs = []
8
+ await eachPlugins(async function ({ plugin, pkg, alias }) {
9
+ let item = find(refs, { plugin })
10
+ if (item) throw error('Plugin name clash: \'%s (%s)\' with \'%s (%s)\'', plugin, pkg, item.plugin, item.pkg, { code: 'BAJO_NAME_CLASH' })
11
+ item = find(refs, { alias })
12
+ if (item) throw error('Plugin alias clash: \'%s (%s)\' with \'%s (%s)\'', alias, pkg, item.alias, item.pkg, { code: 'BAJO_ALIAS_CLASH' })
13
+ refs.push({ plugin, alias, pkg })
14
+ })
15
+ this.bajo.pluginRefs = refs
16
+ }
17
+
18
+ export default checkAlias
@@ -0,0 +1,37 @@
1
+ import { reduce, map, trim, keys, intersection, each, camelCase, get } from 'lodash-es'
2
+ import semver from 'semver'
3
+
4
+ async function runner ({ plugin, pkg, dependencies }) {
5
+ const { log, getConfig, error } = this.bajo.helper
6
+ log.trace('Checking dependencies: %s', plugin)
7
+ const config = getConfig()
8
+ const odep = reduce(dependencies, (o, k) => {
9
+ const item = map(k.split('@'), m => trim(m))
10
+ o[item[0]] = item[1]
11
+ return o
12
+ }, {})
13
+ const deps = keys(odep)
14
+ if (deps.length > 0) {
15
+ if (intersection(config.plugins, deps).length !== deps.length) {
16
+ throw error('Dependency for \'%s\' unfulfilled: %s', pkg, deps.join(', '), { code: 'BAJO_DEPENDENCY' })
17
+ }
18
+ each(deps, d => {
19
+ if (!odep[d]) return
20
+ const ver = get(this[camelCase(d)], 'config.pkg.version')
21
+ if (!ver) return
22
+ if (!semver.satisfies(ver, odep[d])) {
23
+ throw error('Semver check \'%s\' against \'%s\' failed', pkg, `${d}@${odep[d]}`, { code: 'BAJO_DEPENDENCY_SEMVER' })
24
+ }
25
+ })
26
+ }
27
+ }
28
+
29
+ async function checkDependency () {
30
+ const { log, eachPlugins } = this.bajo.helper
31
+ log.debug('Checking dependencies')
32
+ await eachPlugins(async function ({ plugin, pkg, dependencies }) {
33
+ await runner.call(this, { plugin, pkg, dependencies })
34
+ })
35
+ }
36
+
37
+ export default checkDependency
@@ -0,0 +1,25 @@
1
+ import { isFunction, isPlainObject, map } from 'lodash-es'
2
+ import fs from 'fs-extra'
3
+
4
+ async function collectConfigHandlers (pkg) {
5
+ const { getModuleDir, importModule, log } = this.bajo.helper
6
+ for (const pkg of this.bajo.config.plugins) {
7
+ let dir
8
+ try {
9
+ dir = getModuleDir(pkg)
10
+ } catch (err) {}
11
+ if (!dir) continue
12
+ const file = `${dir}/bajo/extend/read-config.js`
13
+ if (!fs.existsSync(file)) continue
14
+ try {
15
+ let mod = await importModule(file)
16
+ if (isFunction(mod)) mod = await mod.call(this)
17
+ if (isPlainObject(mod)) mod = [mod]
18
+ this.bajo.configHandlers.concat(mod)
19
+ } catch (err) {}
20
+ }
21
+ const exts = map(this.bajo.configHandlers, 'ext')
22
+ log.trace('Config handlers: %s', exts.join(', '))
23
+ }
24
+
25
+ export default collectConfigHandlers
@@ -0,0 +1,23 @@
1
+ import {} from 'lodash-es'
2
+ import fs from 'fs-extra'
3
+
4
+ async function collectExitHandlers () {
5
+ const { importModule, log, eachPlugins, getConfig, print } = this.bajo.helper
6
+ const config = getConfig()
7
+ if (!config.exitHandler) return
8
+ this.bajo.exitHandler = this.bajo.exitHandler ?? {}
9
+ const names = []
10
+ await eachPlugins(async function ({ plugin, dir }) {
11
+ const file = `${dir}/bajo/exit.js`
12
+ if (!fs.existsSync(file)) return undefined
13
+ try {
14
+ const mod = await importModule(file)
15
+ this.bajo.exitHandler[plugin] = mod
16
+ names.push(plugin)
17
+ } catch (err) {
18
+ }
19
+ })
20
+ log.trace('Exit handlers: %s', names.length === 0 ? print.__('none') : names.join(', '))
21
+ }
22
+
23
+ export default collectExitHandlers
@@ -0,0 +1,33 @@
1
+ import { map, camelCase, merge, filter, groupBy } from 'lodash-es'
2
+
3
+ async function collectHooks () {
4
+ const { eachPlugins, log, runHook, isLogInRange, importModule } = this.bajo.helper
5
+ this.bajo.hooks = this.bajo.hooks ?? []
6
+ log.debug('Collect hooks')
7
+ // collects
8
+ await eachPlugins(async function ({ plugin, dir, file }) {
9
+ const hookName = (file.slice(dir.length + 1) ?? '').split('/')[1]
10
+ let [ns, path] = map(hookName.replace('.js', '').split('@'), e => camelCase(e))
11
+ if (!path) {
12
+ path = ns
13
+ ns = plugin
14
+ }
15
+ const mod = await importModule(file, { asHandler: true })
16
+ if (!mod) return undefined
17
+ merge(mod, { ns, path })
18
+ this.bajo.hooks.push(mod)
19
+ }, { glob: 'hook/**/*.js', insideBajo: true })
20
+ await runHook('bajo:afterCollectHooks')
21
+ // for log trace purpose only
22
+ if (!isLogInRange('trace')) return
23
+ await eachPlugins(async function ({ plugin }) {
24
+ const hooks = filter(this.bajo.hooks, { ns: plugin })
25
+ if (hooks.length === 0) return undefined
26
+ const items = groupBy(hooks, 'path')
27
+ for (const hook of hooks) {
28
+ log.trace('Collect hook: %s:%s (%d)', hook.ns, hook.path, items[hook.path].length)
29
+ }
30
+ })
31
+ }
32
+
33
+ export default collectHooks
@@ -0,0 +1,21 @@
1
+ import { isArray, each, isPlainObject, has, merge, concat } from 'lodash-es'
2
+ import { readAllConfigs } from './build-config.js'
3
+
4
+ async function extendConfig () {
5
+ const { eachPlugins } = this.bajo.helper
6
+ await eachPlugins(async function (opts) {
7
+ if (!opts.cfg.mergeProps) return undefined
8
+ await eachPlugins(async function ({ dir, plugin }) {
9
+ const cfg = await readAllConfigs.call(this, `${dir}/${opts.plugin}/config`, opts.plugin)
10
+ each(opts.cfg.mergeProps, p => {
11
+ if (!has(cfg, p)) return undefined
12
+ if (isArray(opts.cfg[p])) this[opts.plugin].config[p] = concat(opts.cfg[p], cfg[p])
13
+ else if (isPlainObject(opts.cfg[p])) this[opts.plugin].config[p] = merge(opts.cfg[p], cfg[p])
14
+ else this[opts.plugin].config[p] = cfg[p]
15
+ })
16
+ })
17
+ delete this[opts.plugin].config.mergeProps
18
+ })
19
+ }
20
+
21
+ export default extendConfig