bajo 1.2.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jsdoc.conf.json +6 -3
- package/README.md +8 -148
- package/class/app.js +113 -0
- package/{boot/class/bajo-core.js → class/bajo.js} +448 -35
- package/{boot/class/error.js → class/base/err.js} +29 -3
- package/class/base/log.js +205 -0
- package/class/base/plugin.js +177 -0
- package/class/base/print.js +272 -0
- package/class/helper/bajo.js +344 -0
- package/class/helper/plugin.js +169 -0
- package/{boot/class/bajo-plugin.js → class/plugin.js} +60 -3
- package/docs/App.html +3 -0
- package/docs/Bajo.html +15 -0
- package/docs/BasePlugin.html +5 -0
- package/docs/Err.html +3 -0
- package/docs/Log.html +3 -0
- package/docs/Plugin.html +3 -0
- package/docs/Print.html +3 -0
- package/docs/bitcoin.jpeg +0 -0
- package/docs/class_app.js.html +116 -0
- package/docs/class_bajo.js.html +1100 -0
- package/docs/class_base_err.js.html +99 -0
- package/docs/class_base_log.js.html +208 -0
- package/docs/class_base_plugin.js.html +180 -0
- package/docs/class_base_print.js.html +275 -0
- package/docs/class_helper_bajo.js.html +347 -0
- package/docs/class_helper_plugin.js.html +172 -0
- package/docs/class_plugin.js.html +121 -0
- package/docs/data/search.json +1 -0
- package/docs/fonts/Inconsolata-Regular.ttf +0 -0
- package/docs/fonts/OpenSans-Regular.ttf +0 -0
- package/docs/fonts/WorkSans-Bold.ttf +0 -0
- package/docs/global.html +3 -0
- package/docs/index.html +3 -0
- package/docs/lib_create-method.js.html +42 -0
- package/docs/lib_formats.js.html +68 -0
- package/docs/lib_log-levels.js.html +28 -0
- package/docs/lib_resolve-path.js.html +30 -0
- package/docs/lib_shim.js.html +35 -0
- package/docs/module-class_helper_bajo.html +3 -0
- package/docs/module-class_helper_plugin.html +3 -0
- package/docs/module-lib_create-method.html +3 -0
- package/docs/module-lib_formats.html +3 -0
- package/docs/module-lib_log-levels.html +3 -0
- package/docs/module-lib_resolve-path.html +3 -0
- package/docs/module-lib_shim.html +3 -0
- package/docs/scripts/core.js +726 -0
- package/docs/scripts/core.min.js +23 -0
- package/docs/scripts/resize.js +90 -0
- package/docs/scripts/search.js +265 -0
- package/docs/scripts/search.min.js +6 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
- package/docs/scripts/third-party/fuse.js +9 -0
- package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
- package/docs/scripts/third-party/hljs-line-num.js +1 -0
- package/docs/scripts/third-party/hljs-original.js +5171 -0
- package/docs/scripts/third-party/hljs.js +1 -0
- package/docs/scripts/third-party/popper.js +5 -0
- package/docs/scripts/third-party/tippy.js +1 -0
- package/docs/scripts/third-party/tocbot.js +672 -0
- package/docs/scripts/third-party/tocbot.min.js +1 -0
- package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
- package/docs/styles/clean-jsdoc-theme-light.css +482 -0
- package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
- package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
- package/docs/styles/clean-jsdoc-theme.min.css +1 -0
- package/docs/tutorial-contribution.html +3 -0
- package/docs/tutorial-ecosystem.html +3 -0
- package/docs/tutorial-getting-started.html +13 -0
- package/docs/tutorial-plugin-dev.html +3 -0
- package/docs/tutorial-user-guide.html +3 -0
- package/lib/create-method.js +39 -0
- package/{boot/lib → lib}/formats.js +28 -0
- package/{boot/lib → lib}/log-levels.js +16 -0
- package/{boot/lib → lib}/parse-args-argv.js +1 -1
- package/{boot/lib → lib}/resolve-path.js +12 -0
- package/{boot/lib → lib}/shim.js +10 -0
- package/misc-docs/bitcoin.jpeg +0 -0
- package/misc-docs/contribution.md +20 -0
- package/{docs → misc-docs}/ecosystem.md +41 -12
- package/misc-docs/getting-started.md +142 -0
- package/misc-docs/plugin-dev.md +0 -0
- package/misc-docs/toc.json +17 -0
- package/misc-docs/user-guide.md +1 -0
- package/package.json +14 -13
- package/bajoBook/book/doc/.metadata.json +0 -28
- package/bajoBook/book/doc/How-to-Make-a-Paper-Boat-564x400@2x.jpg +0 -0
- package/bajoBook/book/doc/pages/guides/definition.md +0 -7
- package/bajoBook/book/doc/pages/guides/intro.md +0 -3
- package/bajoBook/book/doc/pages/guides/setup.md +0 -22
- package/bajoBook/book/reference/.metadata.json +0 -152
- package/bajoBook/book/reference/concept-leadership-business-with-paper-boats.jpg +0 -0
- package/bajoBook/book/reference/pages/configuration/configuration-file.md +0 -52
- package/bajoBook/book/reference/pages/helper/break-ns-path.md +0 -24
- package/bajoBook/book/reference/pages/helper/build-collections.md +0 -19
- package/bajoBook/book/reference/pages/helper/call-helper-or-handler.md +0 -35
- package/bajoBook/book/reference/pages/helper/current-loc.md +0 -28
- package/bajoBook/book/reference/pages/helper/dayjs.md +0 -13
- package/bajoBook/book/reference/pages/helper/defaults-deep.md +0 -29
- package/bajoBook/book/reference/pages/helper/dump.md +0 -32
- package/bajoBook/book/reference/pages/helper/each-plugins.md +0 -24
- package/bajoBook/book/reference/pages/helper/envs.md +0 -11
- package/bajoBook/book/reference/pages/helper/error.md +0 -29
- package/bajoBook/book/reference/pages/helper/fatal.md +0 -18
- package/bajoBook/book/reference/pages/helper/freeze.md +0 -13
- package/bajoBook/book/reference/pages/helper/generate-id.md +0 -36
- package/bajoBook/book/reference/pages/helper/get-config.md +0 -27
- package/bajoBook/book/reference/pages/helper/get-global-module-dir.md +0 -13
- package/bajoBook/book/reference/pages/helper/get-helper.md +0 -13
- package/bajoBook/book/reference/pages/helper/get-item-by-name.md +0 -13
- package/bajoBook/book/reference/pages/helper/get-key-by-value.md +0 -13
- package/bajoBook/book/reference/pages/helper/get-module-dir.md +0 -13
- package/bajoBook/book/reference/pages/helper/get-plugin-data-dir.md +0 -13
- package/bajoBook/book/reference/pages/helper/get-plugin-name.md +0 -13
- package/bajoBook/book/reference/pages/helper/get-plugin.md +0 -13
- package/bajoBook/book/reference/pages/helper/import-module.md +0 -13
- package/bajoBook/book/reference/pages/helper/import-pkg.md +0 -13
- package/bajoBook/book/reference/pages/helper/is-empty-dir.md +0 -13
- package/bajoBook/book/reference/pages/helper/is-log-in-range.md +0 -13
- package/bajoBook/book/reference/pages/helper/is-set.md +0 -13
- package/bajoBook/book/reference/pages/helper/is-valid-app.md +0 -13
- package/bajoBook/book/reference/pages/helper/is-valid-plugin.md +0 -13
- package/bajoBook/book/reference/pages/helper/log-levels.md +0 -13
- package/bajoBook/book/reference/pages/helper/log.md +0 -13
- package/bajoBook/book/reference/pages/helper/paginate.md +0 -13
- package/bajoBook/book/reference/pages/helper/pascal-case.md +0 -13
- package/bajoBook/book/reference/pages/helper/print.md +0 -13
- package/bajoBook/book/reference/pages/helper/read-config.md +0 -13
- package/bajoBook/book/reference/pages/helper/read-json.md +0 -13
- package/bajoBook/book/reference/pages/helper/resolve-path.md +0 -13
- package/bajoBook/book/reference/pages/helper/resolve-tpl-path.md +0 -13
- package/bajoBook/book/reference/pages/helper/run-hook.md +0 -13
- package/bajoBook/book/reference/pages/helper/save-as-download.md +0 -13
- package/bajoBook/book/reference/pages/helper/titleize.md +0 -13
- package/bajoBook/book/reference/pages/helper/white-space.md +0 -13
- package/boot/class/app.js +0 -67
- package/boot/class/bajo-core/boot-order.js +0 -35
- package/boot/class/bajo-core/boot-plugins.js +0 -17
- package/boot/class/bajo-core/build-config.js +0 -86
- package/boot/class/bajo-core/build-plugins.js +0 -44
- package/boot/class/bajo-core/collect-config-handlers.js +0 -20
- package/boot/class/bajo-core/exit-handler.js +0 -53
- package/boot/class/bajo-core/run-as-applet.js +0 -26
- package/boot/class/bajo-plugin/attach-method.js +0 -14
- package/boot/class/bajo-plugin/build-config.js +0 -15
- package/boot/class/bajo-plugin/check-clash.js +0 -18
- package/boot/class/bajo-plugin/check-dependency.js +0 -39
- package/boot/class/bajo-plugin/collect-hooks.js +0 -31
- package/boot/class/bajo-plugin/run.js +0 -23
- package/boot/class/log.js +0 -96
- package/boot/class/plugin.js +0 -79
- package/boot/class/print.js +0 -153
- package/boot/lib/create-method.js +0 -33
- package/test/method/isSet.js +0 -43
- /package/{bajo → extend/bajo}/intl/en-US.json +0 -0
- /package/{bajo → extend/bajo}/intl/id.json +0 -0
- /package/{waibuStatic → extend/waibuStatic}/virtual.json +0 -0
- /package/{boot/index.js → index.js} +0 -0
- /package/{boot/lib → lib}/current-loc.js +0 -0
- /package/{boot/lib → lib}/dayjs.js +0 -0
- /package/{boot/lib → lib}/import-module.js +0 -0
- /package/{boot/lib → lib}/omitted-plugin-keys.js +0 -0
- /package/{boot/lib → lib}/parse-env.js +0 -0
- /package/{boot/lib → lib}/read-all-configs.js +0 -0
- /package/{docs/hook.md → misc-docs/.hook.md} +0 -0
|
@@ -0,0 +1,1100 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Source: class/bajo.js</title><!--[if lt IE 9]>
|
|
2
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
3
|
+
<![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(locationPathname=document.location.pathname).substr(0,locationPathname.lastIndexOf("/")+1)</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="light"><div class="sidebar-container"><div class="sidebar" id="sidebar"><a href="/" class="sidebar-title sidebar-title-anchor">Bajo Framework</a><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-class_helper_bajo.html">class/helper/bajo</a></div><div class="sidebar-section-children"><a href="module-class_helper_plugin.html">class/helper/plugin</a></div><div class="sidebar-section-children"><a href="module-lib_create-method.html">lib/create-method</a></div><div class="sidebar-section-children"><a href="module-lib_formats.html">lib/formats</a></div><div class="sidebar-section-children"><a href="module-lib_log-levels.html">lib/log-levels</a></div><div class="sidebar-section-children"><a href="module-lib_resolve-path.html">lib/resolve-path</a></div><div class="sidebar-section-children"><a href="module-lib_shim.html">lib/shim</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="App.html">App</a></div><div class="sidebar-section-children"><a href="Bajo.html">Bajo</a></div><div class="sidebar-section-children"><a href="BasePlugin.html">BasePlugin</a></div><div class="sidebar-section-children"><a href="Err.html">Err</a></div><div class="sidebar-section-children"><a href="Log.html">Log</a></div><div class="sidebar-section-children"><a href="Plugin.html">Plugin</a></div><div class="sidebar-section-children"><a href="Print.html">Print</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-tutorials"><div>Tutorials</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="tutorial-contribution.html">Contribution</a></div><div class="sidebar-section-children"><a href="tutorial-ecosystem.html">Ecosystem</a></div><div class="sidebar-section-children"><a href="tutorial-getting-started.html">Getting Started</a></div><div class="sidebar-section-children"><a href="tutorial-plugin-dev.html">Plugin Development</a></div><div class="sidebar-section-children"><a href="tutorial-user-guide.html">User Guide</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#NsPathType">NsPathType</a></div><div class="sidebar-section-children"><a href="global.html#ObjectFormatType">ObjectFormatType</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"><div class="navbar-item"><a id="" href="https://bajo.app" target="">Bajo.app</a></div><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/bajo" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/bajo" target="">Github</a></div></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">class_bajo.js</h1></header><article><pre class="prettyprint source lang-js"><code>import Plugin from './plugin.js'
|
|
4
|
+
import BasePlugin from './base/plugin.js'
|
|
5
|
+
import increment from 'add-filename-increment'
|
|
6
|
+
import fs from 'fs-extra'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import os from 'os'
|
|
9
|
+
import ms from 'ms'
|
|
10
|
+
import dotenvParseVariables from 'dotenv-parse-variables'
|
|
11
|
+
import emptyDir from 'empty-dir'
|
|
12
|
+
import lodash from 'lodash'
|
|
13
|
+
import currentLoc from '../lib/current-loc.js'
|
|
14
|
+
import { createRequire } from 'module'
|
|
15
|
+
import getGlobalPath from 'get-global-path'
|
|
16
|
+
import { customAlphabet } from 'nanoid'
|
|
17
|
+
import fastGlob from 'fast-glob'
|
|
18
|
+
import querystring from 'querystring'
|
|
19
|
+
import deepFreeze from 'deep-freeze-strict'
|
|
20
|
+
import resolvePath from '../lib/resolve-path.js'
|
|
21
|
+
import importModule from '../lib/import-module.js'
|
|
22
|
+
import logLevels from '../lib/log-levels.js'
|
|
23
|
+
import { types as formatTypes, formats } from '../lib/formats.js'
|
|
24
|
+
|
|
25
|
+
const require = createRequire(import.meta.url)
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
isFunction, map, isObject,
|
|
29
|
+
trim, filter, isEmpty, orderBy, pullAt, find, camelCase, isNumber,
|
|
30
|
+
cloneDeep, isPlainObject, isArray, isString, set, omit, keys, indexOf,
|
|
31
|
+
last, get, has, values, dropRight, pick
|
|
32
|
+
} = lodash
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The Core. The main engine. The one and only plugin that control app's boot process and
|
|
36
|
+
* making sure all other plugins working smoothly.
|
|
37
|
+
*
|
|
38
|
+
* @class
|
|
39
|
+
*/
|
|
40
|
+
class Bajo extends BasePlugin {
|
|
41
|
+
/**
|
|
42
|
+
* @param {Object} app
|
|
43
|
+
* @param {Object} app - App instance reference. Usefull to call app method inside a plugin
|
|
44
|
+
*/
|
|
45
|
+
constructor (app) {
|
|
46
|
+
super('bajo', app)
|
|
47
|
+
/**
|
|
48
|
+
* Date/time when your app start
|
|
49
|
+
* @type {Date}
|
|
50
|
+
*/
|
|
51
|
+
this.runAt = new Date()
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Your main namespace. And yes, you suppose NOT to change this
|
|
55
|
+
*
|
|
56
|
+
* @type {string}
|
|
57
|
+
*/
|
|
58
|
+
this.mainNs = 'main'
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Storage for applets
|
|
62
|
+
*
|
|
63
|
+
* @type {Array}
|
|
64
|
+
*/
|
|
65
|
+
this.applets = []
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* List of all loaded plugin's package names
|
|
69
|
+
*
|
|
70
|
+
* @type {Array}
|
|
71
|
+
*/
|
|
72
|
+
this.pluginPkgs = []
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* List of all loaded plugin's names
|
|
76
|
+
*
|
|
77
|
+
* @type {Array}
|
|
78
|
+
*/
|
|
79
|
+
this.pluginNames = []
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Storage for config handlers. By default there are only two handlers available: ```.js```
|
|
83
|
+
* and ```.json```.
|
|
84
|
+
*
|
|
85
|
+
* Use plugin to add more type - e.g: {@link https://github.com/ardhi/bajo-config|bajo-config}
|
|
86
|
+
* lets you to use ```.yaml``` and ```.toml```
|
|
87
|
+
*
|
|
88
|
+
* @type {Array}
|
|
89
|
+
*/
|
|
90
|
+
this.configHandlers = [
|
|
91
|
+
{ ext: '.js', readHandler: this._defConfigHandler },
|
|
92
|
+
{ ext: '.json', readHandler: this.readJson }
|
|
93
|
+
]
|
|
94
|
+
this.whiteSpace = [' ', '\t', '\n', '\r']
|
|
95
|
+
this.envs = { dev: 'development', staging: 'staging', prod: 'production' }
|
|
96
|
+
this.lib.Plugin = Plugin
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async _defConfigHandler (file, opts = {}) {
|
|
100
|
+
let mod = await importModule(file)
|
|
101
|
+
if (isFunction(mod)) mod = await mod.call(this, opts)
|
|
102
|
+
return mod
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resolve file name to filesystem's path. Windows path separator ```\```
|
|
107
|
+
* is normalized to Unix's ```/```
|
|
108
|
+
*
|
|
109
|
+
* @method
|
|
110
|
+
* @param {string} file - File to resolve
|
|
111
|
+
* @param {boolean} [asFileUrl=false] - Return as file URL format ```file:///<name>```
|
|
112
|
+
* @returns {string}
|
|
113
|
+
*/
|
|
114
|
+
resolvePath = (file, asFileUrl) => {
|
|
115
|
+
return resolvePath(file, asFileUrl)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Freeze object
|
|
120
|
+
*
|
|
121
|
+
* @method
|
|
122
|
+
* @param {Object} obj - Object
|
|
123
|
+
* @param {boolean} [shallow=false] - If true (default), deep freeze object
|
|
124
|
+
*/
|
|
125
|
+
freeze = (obj, shallow) => {
|
|
126
|
+
if (shallow) Object.freeze(obj)
|
|
127
|
+
else deepFreeze(obj)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setImmediate = async () => {
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
setImmediate(() => resolve())
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
breakNsPathFromFile = ({ file, dir, baseNs, suffix = '', getType } = {}) => {
|
|
137
|
+
let item = file.replace(dir + suffix, '')
|
|
138
|
+
let type
|
|
139
|
+
if (getType) {
|
|
140
|
+
const items = item.split('/')
|
|
141
|
+
type = items.shift()
|
|
142
|
+
item = items.join('/')
|
|
143
|
+
}
|
|
144
|
+
item = item.slice(0, item.length - path.extname(item).length)
|
|
145
|
+
let [name, _path] = item.split('@')
|
|
146
|
+
if (!_path) {
|
|
147
|
+
_path = name
|
|
148
|
+
name = baseNs
|
|
149
|
+
}
|
|
150
|
+
_path = camelCase(_path)
|
|
151
|
+
const names = map(name.split('.'), n => camelCase(n))
|
|
152
|
+
const [ns, subNs] = names
|
|
153
|
+
return { ns, subNs, path: _path, fullNs: names.join('.'), type }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
buildNsPath = ({ ns = '', subNs, subSubNs, path } = {}) => {
|
|
157
|
+
if (subNs) ns += '.' + subNs
|
|
158
|
+
if (subSubNs) ns += '.' + subSubNs
|
|
159
|
+
return `${ns}:${path}`
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Object returned by {@link BajoCore#breakNsPath|breakNsPath}
|
|
164
|
+
*
|
|
165
|
+
* @typedef {Object} NsPathType
|
|
166
|
+
* @property {string} ns - Namespace
|
|
167
|
+
* @property {string} [subNs] - Sub namespace
|
|
168
|
+
* @property {string} [subSubNs] - Sub of sub namespace
|
|
169
|
+
* @property {string} path - Path without query string or hash
|
|
170
|
+
* @property {string} fullPath - Full path, including query string and hash
|
|
171
|
+
*/
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Break name to its namespace & path infos
|
|
175
|
+
*
|
|
176
|
+
* @method
|
|
177
|
+
* @param {string} name - Name to break
|
|
178
|
+
* @param {boolean} [checkNs=true] - If true (default), namespace will be checked for its validity
|
|
179
|
+
* @returns {NsPathType}
|
|
180
|
+
*/
|
|
181
|
+
breakNsPath = (name = '', checkNs = true) => {
|
|
182
|
+
let [ns, ...path] = name.split(':')
|
|
183
|
+
const fullNs = ns
|
|
184
|
+
let subNs
|
|
185
|
+
let subSubNs
|
|
186
|
+
path = path.join(':')
|
|
187
|
+
if (path.startsWith('//')) {
|
|
188
|
+
return { path: name } // for: http:// etc
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
[ns, subNs, subSubNs] = ns.split('.')
|
|
192
|
+
if (checkNs) {
|
|
193
|
+
if (!this.app[ns]) {
|
|
194
|
+
const plugin = this.getPlugin(ns)
|
|
195
|
+
if (plugin) ns = plugin.name
|
|
196
|
+
}
|
|
197
|
+
if (!this.app[ns]) throw this.error('unknownPluginOrNotLoaded%s')
|
|
198
|
+
}
|
|
199
|
+
let qs
|
|
200
|
+
[path, qs] = path.split('?')
|
|
201
|
+
qs = querystring.parse(qs) ?? {}
|
|
202
|
+
// normalize path
|
|
203
|
+
const parts = path.split('/')
|
|
204
|
+
const realParts = []
|
|
205
|
+
const params = {}
|
|
206
|
+
for (const idx in parts) {
|
|
207
|
+
const part = parts[idx]
|
|
208
|
+
if (part[0] !== ':' || part.indexOf('|') === -1) {
|
|
209
|
+
realParts.push(part)
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
212
|
+
const [key, val] = part.split('|')
|
|
213
|
+
parts[idx] = key
|
|
214
|
+
params[key.slice(1)] = val
|
|
215
|
+
realParts.push(val)
|
|
216
|
+
}
|
|
217
|
+
path = parts.join('/')
|
|
218
|
+
const realPath = realParts.join('/')
|
|
219
|
+
let fullPath = path
|
|
220
|
+
if (!isEmpty(qs)) fullPath += ('?' + querystring.stringify(qs, null, null, { encodeURIComponent: (text) => (text) }))
|
|
221
|
+
let realFullPath = realPath
|
|
222
|
+
if (!isEmpty(qs)) realFullPath += ('?' + querystring.stringify(qs, null, null, { encodeURIComponent: (text) => (text) }))
|
|
223
|
+
return { ns, path, subNs, subSubNs, qs, fullPath, fullNs, realPath, realFullPath }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Method to transform an array or object from config into an array of collection safely.
|
|
228
|
+
*
|
|
229
|
+
* Emitted hooks:
|
|
230
|
+
* 1. ```{ns}:beforeBuildCollection (container)``` - called before collection is built
|
|
231
|
+
* 2. ```{ns}:afterBuildCollection (container, items)``` - called after collection is built
|
|
232
|
+
*
|
|
233
|
+
* @method
|
|
234
|
+
* @async
|
|
235
|
+
* @param {Object} options - Options
|
|
236
|
+
* @param {string} [options.ns] - Namespace. If not provided, defaults to ```bajo```
|
|
237
|
+
* @param {function} [options.handler] - Handler to call while building the collection item
|
|
238
|
+
* @param {Array} [options.dupChecks=[]] - Array of keys to check for duplicates
|
|
239
|
+
* @param {string} options.container - Key used as container name
|
|
240
|
+
* @param {boolean} [options.useDefaultName=true] - If true (default) and ```name``` key is not provided, returned collection will be named ```default```
|
|
241
|
+
* @returns {Array} The collection
|
|
242
|
+
*/
|
|
243
|
+
buildCollections = async (options = {}) => {
|
|
244
|
+
let { ns, handler, dupChecks = [], container, useDefaultName } = options
|
|
245
|
+
useDefaultName = useDefaultName ?? true
|
|
246
|
+
if (!ns) ns = this.name
|
|
247
|
+
const cfg = this.app[ns].getConfig()
|
|
248
|
+
let items = get(cfg, container, [])
|
|
249
|
+
if (!isArray(items)) items = [items]
|
|
250
|
+
this.app[ns].log.trace('collecting%s', this.app[ns].print.write(container))
|
|
251
|
+
await this.runHook(`${ns}:${camelCase('beforeBuildCollection')}`, container)
|
|
252
|
+
const deleted = []
|
|
253
|
+
for (const index in items) {
|
|
254
|
+
const item = items[index]
|
|
255
|
+
if (useDefaultName) {
|
|
256
|
+
if (!has(item, 'name')) {
|
|
257
|
+
if (find(items, { name: 'default' })) throw this.app[ns].error('collExists%s', 'default')
|
|
258
|
+
else item.name = 'default'
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
this.app[ns].log.trace('- %s', item.name)
|
|
262
|
+
const result = await handler.call(this.app[ns], { item, index, cfg })
|
|
263
|
+
if (result) items[index] = result
|
|
264
|
+
else if (result === false) deleted.push(index)
|
|
265
|
+
if (this.app.bajo.applet && item.skipOnTool && !deleted.includes(index)) deleted.push(index)
|
|
266
|
+
}
|
|
267
|
+
if (deleted.length > 0) pullAt(items, deleted)
|
|
268
|
+
|
|
269
|
+
// check for duplicity
|
|
270
|
+
if (dupChecks.length > 0) {
|
|
271
|
+
const checkers = []
|
|
272
|
+
for (const c of items) {
|
|
273
|
+
const checker = JSON.stringify(pick(c, dupChecks))
|
|
274
|
+
if (checkers.includes(checker)) this.app[ns].fatal('oneOrMoreSharedTheSame%s%s', container, this.join(dupChecks.filter(i => !isFunction(i))))
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
await this.runHook(`${ns}:${camelCase('afterBuildCollection')}`, container, items)
|
|
278
|
+
this.app[ns].log.debug('collected%s%d', this.app[ns].print.write(container), items.length)
|
|
279
|
+
return items
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Calling any plugin's method by its name. Name format: ```ns:methodName```.
|
|
284
|
+
* - If name is a string, the corresponding plugin's method will be called with passed args as its parameters
|
|
285
|
+
* - 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
|
|
286
|
+
* - If name is a function, this function will be run under scope with the remaining args
|
|
287
|
+
* - If name is an object and has ```handler``` key in it, this function handler will be instead
|
|
288
|
+
*
|
|
289
|
+
* @method
|
|
290
|
+
* @async
|
|
291
|
+
* @param {(string|Object|function)} name - Method's name, function handler, plain object or plugin instance
|
|
292
|
+
* @param {...any} [args] - One or more arguments passed as parameter to the handler
|
|
293
|
+
* @returns {any} Returned value
|
|
294
|
+
*/
|
|
295
|
+
callHandler = async (item, ...args) => {
|
|
296
|
+
let result
|
|
297
|
+
let scope = this
|
|
298
|
+
if (item instanceof Plugin) {
|
|
299
|
+
scope = item
|
|
300
|
+
item = args.shift()
|
|
301
|
+
}
|
|
302
|
+
const bajo = scope.app.bajo
|
|
303
|
+
if (isString(item)) {
|
|
304
|
+
if (item.startsWith('applet:') && bajo.applets.length > 0) {
|
|
305
|
+
const [, ns, path] = item.split(':')
|
|
306
|
+
const applet = find(bajo.applets, a => (a.ns === ns || a.alias === ns))
|
|
307
|
+
if (applet) result = await bajo.runApplet(applet, path, ...args)
|
|
308
|
+
} else {
|
|
309
|
+
const [ns, method, ...params] = item.split(':')
|
|
310
|
+
const fn = bajo.getMethod(`${ns}:${method}`)
|
|
311
|
+
if (fn) {
|
|
312
|
+
if (params.length > 0) args.unshift(...params)
|
|
313
|
+
result = await fn(...args)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} else if (isFunction(item)) {
|
|
317
|
+
result = await item.call(scope, ...args)
|
|
318
|
+
} else if (isPlainObject(item) && isFunction(item.handler)) {
|
|
319
|
+
result = await item.handler.call(scope, ...args)
|
|
320
|
+
}
|
|
321
|
+
return result
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* This function iterates through all loaded plugins and call the provided handler scoped as the running plugin.
|
|
326
|
+
* And an object with the following key serves as its parameter:
|
|
327
|
+
*
|
|
328
|
+
* - ```file```: file matched the glob pattern
|
|
329
|
+
* - ```dir```: plugin's base directory
|
|
330
|
+
*
|
|
331
|
+
* @method
|
|
332
|
+
* @async
|
|
333
|
+
* @param {function} handler - Function handler. Can be an async function. Scoped to the running plugin
|
|
334
|
+
* @param {(string|Object)} [options={}] - Options. If a string is provided, it serves as the glob pattern, otherwise:
|
|
335
|
+
* @param {(string|Array)} [options.glob] - Glob pattern. If provided,
|
|
336
|
+
* @param {boolean} [options.useBajo=false] - If true, add ```bajo``` to the running plugins too
|
|
337
|
+
* @param {string} [options.prefix=''] - Prepend glob pattern with prefix
|
|
338
|
+
* @param {boolean} [options.noUnderscore=true] - If true (default), matched file with name starts with underscore is ignored
|
|
339
|
+
* @param {any} [options.returnItems] - If true, each value of returned handler call will be saved as an object with running plugin name as its keys
|
|
340
|
+
* @returns {any}
|
|
341
|
+
*/
|
|
342
|
+
eachPlugins = async (handler, options = {}) => {
|
|
343
|
+
if (typeof options === 'string') options = { glob: options }
|
|
344
|
+
const result = {}
|
|
345
|
+
const pluginPkgs = cloneDeep(this.app.bajo.pluginPkgs) ?? []
|
|
346
|
+
const { glob, useBajo, prefix = '', noUnderscore = true, returnItems } = options
|
|
347
|
+
if (useBajo) pluginPkgs.unshift('bajo')
|
|
348
|
+
for (const pkgName of pluginPkgs) {
|
|
349
|
+
const ns = camelCase(pkgName)
|
|
350
|
+
let r
|
|
351
|
+
if (glob) {
|
|
352
|
+
const base = prefix === '' ? `${this.app[ns].dir.pkg}/extend` : `${this.app[ns].dir.pkg}/extend/${prefix}`
|
|
353
|
+
let opts = isString(glob) ? { pattern: [glob] } : glob
|
|
354
|
+
let pattern = opts.pattern ?? []
|
|
355
|
+
if (isString(pattern)) pattern = [pattern]
|
|
356
|
+
opts = omit(opts, ['pattern'])
|
|
357
|
+
for (const i in pattern) {
|
|
358
|
+
if (!path.isAbsolute(pattern[i])) pattern[i] = `${base}/${pattern[i]}`
|
|
359
|
+
}
|
|
360
|
+
const files = await fastGlob(pattern, opts)
|
|
361
|
+
for (const f of files) {
|
|
362
|
+
if (path.basename(f)[0] === '_' && noUnderscore) continue
|
|
363
|
+
const resp = await handler.call(this.app[ns], { file: f, dir: base })
|
|
364
|
+
if (resp === false) break
|
|
365
|
+
else if (resp === undefined) continue
|
|
366
|
+
else {
|
|
367
|
+
result[ns] = result[ns] ?? {}
|
|
368
|
+
result[ns][f] = resp
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
r = await handler.call(this.app[ns], { dir: this.app[ns].dir.pkg })
|
|
373
|
+
if (r === false) break
|
|
374
|
+
else if (r === undefined) continue
|
|
375
|
+
else result[ns] = r
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (returnItems) {
|
|
379
|
+
const data = []
|
|
380
|
+
for (const r in result) {
|
|
381
|
+
for (const f in result[r]) {
|
|
382
|
+
data.push(result[r][f])
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return data
|
|
386
|
+
}
|
|
387
|
+
return result
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Object returned by {@link BajoCore#getUnitFormat|getUnitFormat}
|
|
392
|
+
*
|
|
393
|
+
* @typedef {Object} ObjectFormatType
|
|
394
|
+
* @property {string} unitSys - Unit system
|
|
395
|
+
* @property {Object} format - Format object
|
|
396
|
+
*/
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get unit format
|
|
400
|
+
*
|
|
401
|
+
* @method
|
|
402
|
+
* @param {Object} [options={}] - Options
|
|
403
|
+
* @param {string} [options.lang] - Language to use. Defaults to the one you set in config
|
|
404
|
+
* @param {string} [options.unitSys] - Unit system to use. Defaults to language's unit system or ```metric``` if unspecified
|
|
405
|
+
* @returns {ObjectFormatType} - Returned value
|
|
406
|
+
*/
|
|
407
|
+
getUnitFormat = (options = {}) => {
|
|
408
|
+
const lang = options.lang ?? this.config.lang
|
|
409
|
+
let unitSys = options.unitSys ?? this.config.intl.unitSys[lang] ?? 'metric'
|
|
410
|
+
if (!['imperial', 'nautical', 'metric'].includes(unitSys)) unitSys = 'metric'
|
|
411
|
+
return { unitSys, format: formats[unitSys] }
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Format value by type
|
|
416
|
+
*
|
|
417
|
+
* @method
|
|
418
|
+
* @param {string} type - Format type. See {@link FormatType} for acceptable values
|
|
419
|
+
* @param {any} value - Value to format
|
|
420
|
+
* @param {string} [dataType] - Value's data type. See {@link DataType} for acceptable values
|
|
421
|
+
* @param {Object} [options={}] - Options
|
|
422
|
+
* @param {boolean} [options.withUnit=true] - Return with its unit appended
|
|
423
|
+
* @param {string} [options.lang] - Format value according to this language. Defaults to the one you set in config
|
|
424
|
+
* @returns {(Array|string)} Return string if ```withUnit``` is true. Otherwise is an array of ```[value, unit, separator]```
|
|
425
|
+
*/
|
|
426
|
+
formatByType = (type, value, dataType, options = {}) => {
|
|
427
|
+
const { defaultsDeep } = this.lib.aneka
|
|
428
|
+
const { format } = this.getUnitFormat(options)
|
|
429
|
+
const { withUnit = true } = options
|
|
430
|
+
const lang = options.lang ?? this.config.lang
|
|
431
|
+
value = format[`${type}Fn`](value)
|
|
432
|
+
const unit = format[`${type}Unit`]
|
|
433
|
+
const sep = format[`${type}UnitSep`] ?? ' '
|
|
434
|
+
if (!withUnit) return [value, unit, sep]
|
|
435
|
+
const setting = defaultsDeep(options[dataType], this.config.intl.format[dataType])
|
|
436
|
+
value = new Intl.NumberFormat(lang, setting).format(value)
|
|
437
|
+
return `${value}${sep}${unit}`
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Format value
|
|
442
|
+
*
|
|
443
|
+
* @method
|
|
444
|
+
* @param {any} value - Value to format
|
|
445
|
+
* @param {string} [type] - Data type to use. See {@link DataType} for acceptable values. If not provided, return the untouched value
|
|
446
|
+
* @param {Object} [options={}] - Options
|
|
447
|
+
* @param {string} [options.emptyValue=''] - Empty value to use if function resulted empty. Defaults to the one from your config
|
|
448
|
+
* @param {boolean} [options.withUnit=true] - Return with its unit appended
|
|
449
|
+
* @param {string} [options.lang] - Format value according to this language. Defaults to the one you set in config
|
|
450
|
+
* @param {string} [options.latitude] - If Bajo Spatial is loaded and data type is a double or float, then format it as latitude in degree, minute, second
|
|
451
|
+
* @param {string} [options.longitude] - If Bajo Spatial is loaded and data type is a double or float, then format it as longitude in degree, minute, second
|
|
452
|
+
* @returns {string} Formatted value
|
|
453
|
+
*/
|
|
454
|
+
format = (value, type, options = {}) => {
|
|
455
|
+
const { defaultsDeep } = this.lib.aneka
|
|
456
|
+
const { format } = this.config.intl
|
|
457
|
+
const { emptyValue = format.emptyValue } = options
|
|
458
|
+
const lang = options.lang ?? this.config.lang
|
|
459
|
+
options.withUnit = options.withUnit ?? true
|
|
460
|
+
let valueFormatted
|
|
461
|
+
if ([undefined, null, ''].includes(value)) return emptyValue
|
|
462
|
+
if (type === 'auto') {
|
|
463
|
+
if (value instanceof Date) type = 'datetime'
|
|
464
|
+
}
|
|
465
|
+
if (['float', 'double'].includes(type) && this.app.bajoSpatial) {
|
|
466
|
+
const { latToDms, lngToDms } = this.app.bajoSpatial.lib.anekaSpatial
|
|
467
|
+
if (options.latitude) return latToDms(value)
|
|
468
|
+
if (options.longitude) return lngToDms(value)
|
|
469
|
+
}
|
|
470
|
+
if (['integer', 'smallint', 'float', 'double'].includes(type)) {
|
|
471
|
+
value = ['integer', 'smallint'].includes(type) ? parseInt(value) : parseFloat(value)
|
|
472
|
+
if (isNaN(value)) return emptyValue
|
|
473
|
+
for (const u of formatTypes) {
|
|
474
|
+
if (options[u]) valueFormatted = this.formatByType(u, value, type, options)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (['integer', 'smallint'].includes(type)) {
|
|
478
|
+
const setting = defaultsDeep(options.integer, format.integer)
|
|
479
|
+
value = new Intl.NumberFormat(lang, setting).format(Math.round(value))
|
|
480
|
+
return valueFormatted && options.withUnit ? valueFormatted : value
|
|
481
|
+
}
|
|
482
|
+
if (['float', 'double'].includes(type)) {
|
|
483
|
+
const setting = defaultsDeep(options[type], format[type])
|
|
484
|
+
value = new Intl.NumberFormat(lang, setting).format(value)
|
|
485
|
+
return valueFormatted && options.withUnit ? valueFormatted : value
|
|
486
|
+
}
|
|
487
|
+
if (['datetime', 'date'].includes(type)) {
|
|
488
|
+
const setting = defaultsDeep(options[type], format[type])
|
|
489
|
+
return new Intl.DateTimeFormat(lang, setting).format(new Date(value))
|
|
490
|
+
}
|
|
491
|
+
if (['time'].includes(type)) {
|
|
492
|
+
const setting = defaultsDeep(options.time, format.time)
|
|
493
|
+
return new Intl.DateTimeFormat(lang, setting).format(new Date(`1970-01-01T${value}Z`))
|
|
494
|
+
}
|
|
495
|
+
if (['array'].includes(type)) return value.join(', ')
|
|
496
|
+
if (['object'].includes(type)) return JSON.stringify(value)
|
|
497
|
+
return value
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Generate unique random characters that can be used as ID. Use {@link https://github.com/ai/nanoid|nanoid} under the hood
|
|
502
|
+
*
|
|
503
|
+
* @method
|
|
504
|
+
* @param {(boolean|string|Object)} [options={}] - Options. If set to ```true``` or ```alpha```, it will generate alphaphet only characters. If set to ```int```, it will generate integer only characters. Otherwise:
|
|
505
|
+
* @param {string} [options.pattern] - Character pattern to use. Defaults to all available alphanumeric characters
|
|
506
|
+
* @param {number} [options.length=13] - Length of resulted characters
|
|
507
|
+
* @param {string} [options.case] - If set to ```lower``` to use lower cased pattern only. For upper cased pattern, set it to ```upper```
|
|
508
|
+
* @param {boolean} [options.returnInstance] - Set to ```true``` to return {@link https://github.com/ai/nanoid|nanoid} instance instead of string
|
|
509
|
+
* @returns {(string|Object)} Return string or instance of {@link https://github.com/ai/nanoid|nanoid}
|
|
510
|
+
*/
|
|
511
|
+
generateId = (options = {}) => {
|
|
512
|
+
let type
|
|
513
|
+
if (options === true) options = 'alpha'
|
|
514
|
+
if (options === 'int') {
|
|
515
|
+
type = options
|
|
516
|
+
options = { pattern: '0123456789', length: 15 }
|
|
517
|
+
} else if (options === 'alpha') {
|
|
518
|
+
type = options
|
|
519
|
+
options = { pattern: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', length: 15 }
|
|
520
|
+
}
|
|
521
|
+
let { pattern, length = 13, returnInstance } = options
|
|
522
|
+
pattern = pattern ?? 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
523
|
+
if (options.case === 'lower') pattern = pattern.toLowerCase()
|
|
524
|
+
else if (options.case === 'upper') pattern = pattern.toUpperCase()
|
|
525
|
+
const nid = customAlphabet(pattern, length)
|
|
526
|
+
if (returnInstance) return nid
|
|
527
|
+
const value = nid()
|
|
528
|
+
return type === 'int' ? parseInt(value) : value
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Get NPM global module directory
|
|
533
|
+
*
|
|
534
|
+
* @method
|
|
535
|
+
* @param {string} [pkgName] - If provided, return this package global directory. Otherwise the npm global module directory
|
|
536
|
+
* @param {boolean} [silent=true] - Set to ```false``` to throw exception in case of error. Otherwise silently returns undefined
|
|
537
|
+
* @returns {string}
|
|
538
|
+
*/
|
|
539
|
+
getGlobalModuleDir = (pkgName, silent = true) => {
|
|
540
|
+
let nodeModulesDir = process.env.BAJO_GLOBAL_MODULE_DIR
|
|
541
|
+
if (!nodeModulesDir) {
|
|
542
|
+
const npmPath = getGlobalPath('npm')
|
|
543
|
+
if (!npmPath) {
|
|
544
|
+
if (silent) return
|
|
545
|
+
throw this.error('cantLocateNpmGlobalDir', { code: 'BAJO_CANT_LOCATE_NPM_GLOBAL_DIR' })
|
|
546
|
+
}
|
|
547
|
+
nodeModulesDir = dropRight(resolvePath(npmPath).split('/'), 1).join('/')
|
|
548
|
+
process.env.BAJO_GLOBAL_MODULE_DIR = nodeModulesDir
|
|
549
|
+
}
|
|
550
|
+
if (!pkgName) return nodeModulesDir
|
|
551
|
+
const dir = `${nodeModulesDir}/${pkgName}`
|
|
552
|
+
if (!fs.existsSync(dir)) {
|
|
553
|
+
if (silent) return
|
|
554
|
+
throw this.error('cantLocateGlobalDir%s', pkgName, { code: 'BAJO_CANT_LOCATE_MODULE_GLOBAL_DIR' })
|
|
555
|
+
}
|
|
556
|
+
return dir
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Get class method by name
|
|
561
|
+
*
|
|
562
|
+
* @method
|
|
563
|
+
* @param {string} name - Name in format ```ns:methodName```
|
|
564
|
+
* @param {boolean} [thrown=true] - If ```true``` (default), throw exceptiom in case of error
|
|
565
|
+
* @returns {function} Class method
|
|
566
|
+
*/
|
|
567
|
+
getMethod = (name = '', thrown = true) => {
|
|
568
|
+
const { ns, path } = this.breakNsPath(name)
|
|
569
|
+
const method = get(this.app, `${ns}.${path}`)
|
|
570
|
+
if (method && isFunction(method)) return method
|
|
571
|
+
if (thrown) throw this.error('cantFindMethod%s', name)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Find item deep in paths
|
|
576
|
+
*
|
|
577
|
+
* @method
|
|
578
|
+
* @param {string} item - Item to find
|
|
579
|
+
* @param {Array} paths - Array of path to look for
|
|
580
|
+
* @returns {string}
|
|
581
|
+
*/
|
|
582
|
+
findDeep = (item, paths) => {
|
|
583
|
+
let dir
|
|
584
|
+
for (const p of paths) {
|
|
585
|
+
const d = `${p}/${item}`
|
|
586
|
+
if (fs.existsSync(d)) {
|
|
587
|
+
dir = d
|
|
588
|
+
break
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return dir
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Get module directory, locally and globally
|
|
596
|
+
*
|
|
597
|
+
* @method
|
|
598
|
+
* @param {string} pkgName - Package name to find
|
|
599
|
+
* @param {*} base - Provide base name if ```pkgName``` is a module under ```base```'s package name
|
|
600
|
+
* @returns {string} Return absolute package directory
|
|
601
|
+
*/
|
|
602
|
+
getModuleDir = (pkgName, base) => {
|
|
603
|
+
if (pkgName === 'main') return resolvePath(this.app.dir)
|
|
604
|
+
if (base === 'main') base = this.app.dir
|
|
605
|
+
else if (this && this.app && this.app[base]) base = this.app[base].pkgName
|
|
606
|
+
const pkgPath = pkgName + '/package.json'
|
|
607
|
+
const paths = require.resolve.paths(pkgPath)
|
|
608
|
+
const gdir = this.getGlobalModuleDir()
|
|
609
|
+
paths.unshift(gdir)
|
|
610
|
+
paths.unshift(resolvePath(path.join(this.app.dir, 'node_modules')))
|
|
611
|
+
let dir = this.findDeep(pkgPath, paths)
|
|
612
|
+
if (base && !dir) dir = this.findDeep(`${base}/node_modules/${pkgPath}`, paths)
|
|
613
|
+
if (!dir) return null
|
|
614
|
+
return resolvePath(path.dirname(dir))
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Get plugin data directory
|
|
619
|
+
*
|
|
620
|
+
* @method
|
|
621
|
+
* @param {string} name - Plugin name (namespace) or alias
|
|
622
|
+
* @param {boolean} [ensureDir=true] - Set ```true``` (default) to ensure directory is existed
|
|
623
|
+
* @returns {string}
|
|
624
|
+
*/
|
|
625
|
+
getPluginDataDir = (name, ensureDir = true) => {
|
|
626
|
+
const plugin = this.getPlugin(name)
|
|
627
|
+
const dir = `${this.app.bajo.dir.data}/plugins/${plugin.name}`
|
|
628
|
+
if (ensureDir) fs.ensureDirSync(dir)
|
|
629
|
+
return dir
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Resolve file path from:
|
|
634
|
+
*
|
|
635
|
+
* - local/absolute file
|
|
636
|
+
* - ns based path (```myPlugin:/path/to/file.txt```)
|
|
637
|
+
*
|
|
638
|
+
* @method
|
|
639
|
+
* @param {string} file - File path, see above for supported types
|
|
640
|
+
* @returns {string} Resolved file path
|
|
641
|
+
*/
|
|
642
|
+
getPluginFile = (file) => {
|
|
643
|
+
if (!this) return file
|
|
644
|
+
if (file[0] === '.') file = `${currentLoc(import.meta).dir}/${trim(file.slice(1), '/')}`
|
|
645
|
+
if (file.includes(':')) {
|
|
646
|
+
if (file.slice(1, 2) === ':') return file // windows fs
|
|
647
|
+
const { ns, path } = this.breakNsPath(file)
|
|
648
|
+
if (ns !== 'file' && this && this.app && this.app[ns] && ns.length > 1) {
|
|
649
|
+
file = `${this.app[ns].dir.pkg}${path}`
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return file
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Get plugin by name
|
|
657
|
+
*
|
|
658
|
+
* @method
|
|
659
|
+
* @param {string} name - Plugin name/namespace or alias
|
|
660
|
+
* @param {boolean} [silent] - If ```true```, silently return undefined even on error
|
|
661
|
+
* @returns {Object} Plugin object
|
|
662
|
+
*/
|
|
663
|
+
getPlugin = (name, silent) => {
|
|
664
|
+
if (!this.app[name]) {
|
|
665
|
+
// alias?
|
|
666
|
+
let plugin
|
|
667
|
+
for (const key in this.app) {
|
|
668
|
+
const item = this.app[key]
|
|
669
|
+
if (item instanceof Plugin && (item.alias === name || item.pkgName === name)) {
|
|
670
|
+
plugin = item
|
|
671
|
+
break
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (!plugin) {
|
|
675
|
+
if (silent) return false
|
|
676
|
+
throw this.error('pluginWithNameAliasNotLoaded%s', name)
|
|
677
|
+
}
|
|
678
|
+
name = plugin.name
|
|
679
|
+
}
|
|
680
|
+
return this.app[name]
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Import file/module from any loaded plugins
|
|
685
|
+
*
|
|
686
|
+
* Your plugin structure:
|
|
687
|
+
* ```
|
|
688
|
+
* |- src
|
|
689
|
+
* | |- lib
|
|
690
|
+
* | | |- my-module.js
|
|
691
|
+
* |- index.js
|
|
692
|
+
* |- package.json
|
|
693
|
+
* ```
|
|
694
|
+
*
|
|
695
|
+
* Inside your app/plugin:
|
|
696
|
+
* ```javascript
|
|
697
|
+
* const { importModule } = this.app.bajo
|
|
698
|
+
* const myModule = await importModule('myPlugin:/src/lib/my-module.js')
|
|
699
|
+
* ```
|
|
700
|
+
* @method
|
|
701
|
+
* @async
|
|
702
|
+
* @param {string} file - File in format ```ns:<ns based file path>```
|
|
703
|
+
* @param {Object} [options={}] - Options
|
|
704
|
+
* @param {boolean} [options.asDefaultImport=true] - If ```true``` (default), return default imported module
|
|
705
|
+
* @param {boolean} [options.asHandler] - If ```true```, return as a {@link HandlerType|handler}
|
|
706
|
+
* @param {boolean} [options.noCache] - If ```true```, always import as a fresh copy
|
|
707
|
+
* @returns {(function|Object)}
|
|
708
|
+
*/
|
|
709
|
+
importModule = async (file, { asDefaultImport, asHandler, noCache } = {}) => {
|
|
710
|
+
return await importModule.call(this, file, { asDefaultImport, asHandler, noCache })
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Import one or more package belongs to a plugin
|
|
715
|
+
*
|
|
716
|
+
* Example: you want to import packages ```delay``` and ```chalk``` from ```bajo``` namespace and use it inside your app/plugin
|
|
717
|
+
*
|
|
718
|
+
* ```javascript
|
|
719
|
+
* const { importPkg } from this.app.bajo
|
|
720
|
+
* const [delay, chalk] = await importPkg('bajo:delay', 'bajo:chalk')
|
|
721
|
+
*
|
|
722
|
+
* await delay(1000)
|
|
723
|
+
* ...
|
|
724
|
+
* ```
|
|
725
|
+
*
|
|
726
|
+
* @method
|
|
727
|
+
* @async
|
|
728
|
+
* @param {...any} pkgs - One or more packages in format ```ns:packageName```
|
|
729
|
+
* @returns {(Object|Array)} Depends on how many parameters are provided, it should return the named package or an array of packages
|
|
730
|
+
*/
|
|
731
|
+
importPkg = async (...pkgs) => {
|
|
732
|
+
const { defaultsDeep } = this.lib.aneka
|
|
733
|
+
const result = {}
|
|
734
|
+
const notFound = []
|
|
735
|
+
let opts = { returnDefault: true, thrownNotFound: false }
|
|
736
|
+
if (isPlainObject(last(pkgs))) {
|
|
737
|
+
opts = defaultsDeep(pkgs.pop(), opts)
|
|
738
|
+
}
|
|
739
|
+
for (let pkg of pkgs) {
|
|
740
|
+
if (pkg.indexOf(':') === -1) pkg = `bajo:${pkg}`
|
|
741
|
+
const { ns, path: name } = this.breakNsPath(pkg)
|
|
742
|
+
const dir = this.getModuleDir(name, ns)
|
|
743
|
+
if (!dir) {
|
|
744
|
+
notFound.push(pkg)
|
|
745
|
+
continue
|
|
746
|
+
}
|
|
747
|
+
const p = this.readJson(`${dir}/package.json`, opts.thrownNotFound)
|
|
748
|
+
const mainFileOrg = dir + '/' + (p.main ?? get(p, 'exports.default', 'index.js'))
|
|
749
|
+
let mainFile = resolvePath(mainFileOrg, os.platform() === 'win32')
|
|
750
|
+
if (isEmpty(path.extname(mainFile))) {
|
|
751
|
+
if (fs.existsSync(`${mainFileOrg}/index.js`)) mainFile += '/index.js'
|
|
752
|
+
else mainFile += '.js'
|
|
753
|
+
}
|
|
754
|
+
if (opts.noCache) mainFile += `?_=${Date.now()}`
|
|
755
|
+
let mod = await import(mainFile)
|
|
756
|
+
if (opts.returnDefault && has(mod, 'default')) {
|
|
757
|
+
mod = mod.default
|
|
758
|
+
if (opts.returnDefault && has(mod, 'default')) mod = mod.default
|
|
759
|
+
}
|
|
760
|
+
result[name] = mod
|
|
761
|
+
}
|
|
762
|
+
if (notFound.length > 0) throw this.error('cantFind%s', this.join(notFound))
|
|
763
|
+
if (pkgs.length === 1) return result[keys(result)[0]]
|
|
764
|
+
if (opts.asObject) return result
|
|
765
|
+
return values(result)
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Check whether directory is empty or not. More info please {@link https://github.com/gulpjs/empty-dir|check here}.
|
|
770
|
+
*
|
|
771
|
+
* @method
|
|
772
|
+
* @async
|
|
773
|
+
* @param {string} dir - Directory to check. Can be a ns based directory too!
|
|
774
|
+
* @param {function} filterFn - Filter function to filter out files that cause false positives.
|
|
775
|
+
* @returns {boolean}
|
|
776
|
+
*/
|
|
777
|
+
isEmptyDir = async (dir, filterFn) => {
|
|
778
|
+
dir = resolvePath(this.getPluginFile(dir))
|
|
779
|
+
await fs.exists(dir)
|
|
780
|
+
return await emptyDir(dir, filterFn)
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Check whether log level is within log's app current level
|
|
785
|
+
*
|
|
786
|
+
* @method
|
|
787
|
+
* @param {string} level - Level to check. See {@link LogLevelsType} for more
|
|
788
|
+
* @returns {boolean}
|
|
789
|
+
*/
|
|
790
|
+
isLogInRange = (level) => {
|
|
791
|
+
const levels = keys(logLevels)
|
|
792
|
+
const logLevel = indexOf(levels, this.app.bajo.config.log.level)
|
|
793
|
+
return indexOf(levels, level) >= logLevel
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
isValidAppPlugin = (file, type, returnPkg) => {
|
|
797
|
+
if (isObject(file)) return get(file, 'bajo.type') === type
|
|
798
|
+
file = resolvePath(file)
|
|
799
|
+
if (path.basename(file) !== 'package.json') file += '/package.json'
|
|
800
|
+
try {
|
|
801
|
+
const pkg = fs.readJsonSync(file)
|
|
802
|
+
const valid = get(pkg, 'bajo.type') === type
|
|
803
|
+
if (valid) return returnPkg ? pkg : valid
|
|
804
|
+
return false
|
|
805
|
+
} catch (err) {
|
|
806
|
+
return false
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Check whether directory is a valid Bajo app
|
|
812
|
+
*
|
|
813
|
+
* @method
|
|
814
|
+
* @param {string} dir - Directory to check
|
|
815
|
+
* @param {boolean} [returnPkg] - Set ```true``` to return its package.json content
|
|
816
|
+
* @returns {(boolean|Object)}
|
|
817
|
+
*/
|
|
818
|
+
isValidApp = (dir, returnPkg) => {
|
|
819
|
+
if (!dir) dir = this.app.dir
|
|
820
|
+
return this.isValidAppPlugin(dir, 'app', returnPkg)
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Check whether directory is a valid Bajo plugin
|
|
825
|
+
*
|
|
826
|
+
* @method
|
|
827
|
+
* @param {string} dir - Directory to check
|
|
828
|
+
* @param {boolean} [returnPkg] - Set ```true``` to return its package.json content
|
|
829
|
+
* @returns {(boolean|Object)}
|
|
830
|
+
*/
|
|
831
|
+
isValidPlugin = (dir, returnPkg) => {
|
|
832
|
+
if (!dir) dir = this.app.dir
|
|
833
|
+
return this.isValidAppPlugin(dir, 'plugin', returnPkg)
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
join = (array, sep) => {
|
|
837
|
+
const { isSet } = this.lib.aneka
|
|
838
|
+
const translate = val => {
|
|
839
|
+
if (this && this.print) return this.print.write(val).toLowerCase()
|
|
840
|
+
return val
|
|
841
|
+
}
|
|
842
|
+
if (array.length === 0) return translate('none')
|
|
843
|
+
if (array.length === 1) return array[0]
|
|
844
|
+
if (isSet(sep) && !isPlainObject(sep)) return array.join(sep)
|
|
845
|
+
let { separator = ', ', joiner = 'and' } = sep ?? {}
|
|
846
|
+
joiner = translate(joiner)
|
|
847
|
+
const last = (array.pop() ?? '').trim()
|
|
848
|
+
return array.map(a => (a + '').trim()).join(separator) + ` ${joiner} ${last}`
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Return its numeric portion of a value
|
|
853
|
+
*
|
|
854
|
+
* @method
|
|
855
|
+
* @param {string} [value=''] - Value to get its numeric portion
|
|
856
|
+
* @param {string} [defUnit=''] - Default unit if value doesn't have one
|
|
857
|
+
* @returns {string}
|
|
858
|
+
*/
|
|
859
|
+
numUnit = (value = '', defUnit = '') => {
|
|
860
|
+
const num = value.match(/\d+/g)
|
|
861
|
+
const unit = value.match(/[a-zA-Z]+/g)
|
|
862
|
+
return `${num[0]}${isEmpty(unit) ? defUnit : unit[0]}`
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Parse duration to its millisecond value. Use {@link https://github.com/vercel/ms|ms} under the hood
|
|
867
|
+
*
|
|
868
|
+
* @method
|
|
869
|
+
* @param {(number|string)} dur - If string is given, parse this to its millisecond value. Otherwise return as is
|
|
870
|
+
* @returns {number}
|
|
871
|
+
*/
|
|
872
|
+
parseDur = (dur) => {
|
|
873
|
+
return isNumber(dur) ? dur : ms(dur)
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Parse datetime string as Javascript object. Please visit {@link https://day.js.org|dayjs} for valid formats and more infos
|
|
878
|
+
*
|
|
879
|
+
* @method
|
|
880
|
+
* @param {string} dt - Datetime string
|
|
881
|
+
* @returns {Object} Javascript object
|
|
882
|
+
*/
|
|
883
|
+
parseDt = (dt) => {
|
|
884
|
+
const value = this.lib.dayjs(dt)
|
|
885
|
+
if (!value.isValid()) throw this.error('dtUnparsable%s', dt)
|
|
886
|
+
return value.toDate()
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Parse an object and optionally normalize its values recursively. Use {@link https://github.com/ladjs/dotenv-parse-variables}
|
|
891
|
+
* to parse values, so please have a visit to know how it works
|
|
892
|
+
*
|
|
893
|
+
* If ```options.parseValue``` is ```true```, any key ends with ```Dur``` and ```Dt``` will
|
|
894
|
+
* also be parsed as millisecond and Javascript datetime accordingly
|
|
895
|
+
*
|
|
896
|
+
* @method
|
|
897
|
+
* @param {(Object|string)} input - If string is given, parse it first using JSON.parse
|
|
898
|
+
* @param {Object} [options={}] - Options
|
|
899
|
+
* @param {boolean} [options.silent=true] - If ```true``` (default), exception are not thrown and silently ignored
|
|
900
|
+
* @param {boolean} [options.parseValue=false] - If ```true```, values will be parsed & normalized
|
|
901
|
+
* @param {string} [options.lang] - If provided, use this language instead of the one in config
|
|
902
|
+
* @returns {Object}
|
|
903
|
+
*/
|
|
904
|
+
parseObject = (input, options = {}) => {
|
|
905
|
+
const { silent = true, parseValue = false, lang, ns } = options
|
|
906
|
+
const { isSet } = this.lib.aneka
|
|
907
|
+
const translate = (item) => {
|
|
908
|
+
const scope = ns ? this.app[ns] : this
|
|
909
|
+
const [text, ...args] = item.split('|')
|
|
910
|
+
return scope.print.write(text, ...args, { lang })
|
|
911
|
+
}
|
|
912
|
+
const statics = ['*']
|
|
913
|
+
if (isString(input)) {
|
|
914
|
+
try {
|
|
915
|
+
input = JSON.parse(input)
|
|
916
|
+
} catch (err) {
|
|
917
|
+
if (silent) input = {}
|
|
918
|
+
else throw err
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
let obj = cloneDeep(input)
|
|
922
|
+
const keys = Object.keys(obj)
|
|
923
|
+
const mutated = []
|
|
924
|
+
keys.forEach(k => {
|
|
925
|
+
let v = obj[k]
|
|
926
|
+
if (isPlainObject(v)) obj[k] = this.parseObject(v, options)
|
|
927
|
+
else if (isArray(v)) {
|
|
928
|
+
v.forEach((i, idx) => {
|
|
929
|
+
if (isPlainObject(i)) obj[k][idx] = this.parseObject(i, options)
|
|
930
|
+
else if (statics.includes(i)) obj[k][idx] = i
|
|
931
|
+
else if (parseValue) obj[k][idx] = dotenvParseVariables(set({}, 'item', obj[k][idx]), { assignToProcessEnv: false }).item
|
|
932
|
+
if (isArray(obj[k][idx])) obj[k][idx] = obj[k][idx].map(item => typeof item === 'string' ? item.trim() : item)
|
|
933
|
+
})
|
|
934
|
+
} else if (isSet(v)) {
|
|
935
|
+
if (isString(v) && v.startsWith('t:') && lang) v = translate(v.slice(2))
|
|
936
|
+
try {
|
|
937
|
+
if (statics.includes(v)) obj[k] = v
|
|
938
|
+
else if (k.startsWith('t:') && isString(v)) {
|
|
939
|
+
const newK = k.slice(2)
|
|
940
|
+
if (lang) obj[newK] = translate(v)
|
|
941
|
+
else obj[newK] = v
|
|
942
|
+
mutated.push(k)
|
|
943
|
+
} else if (parseValue) {
|
|
944
|
+
obj[k] = dotenvParseVariables(set({}, 'item', v), { assignToProcessEnv: false }).item
|
|
945
|
+
if (isArray(obj[k])) obj[k] = obj[k].map(item => typeof item === 'string' ? item.trim() : item)
|
|
946
|
+
}
|
|
947
|
+
if (k.slice(-3) === 'Dur') obj[k] = this.parseDur(v)
|
|
948
|
+
if (k.slice(-2) === 'Dt') obj[k] = this.parseDt(v)
|
|
949
|
+
} catch (err) {
|
|
950
|
+
obj[k] = undefined
|
|
951
|
+
if (!silent) throw err
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
})
|
|
955
|
+
if (mutated.length > 0) obj = omit(obj, mutated)
|
|
956
|
+
return obj
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
pick = (obj, items, excludeUnset) => {
|
|
960
|
+
const { isSet } = this.lib.aneka
|
|
961
|
+
const result = {}
|
|
962
|
+
for (const item of items) {
|
|
963
|
+
const [k, nk] = item.split(':')
|
|
964
|
+
if (excludeUnset && !isSet(obj[k])) continue
|
|
965
|
+
result[nk ?? k] = obj[k]
|
|
966
|
+
}
|
|
967
|
+
return result
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Read and parse file as config object. Supported types: ```.js``` and ```.json```.
|
|
972
|
+
* More supports can be added using plugin. {@link https://github.com/ardhi/bajo-config|bajo-config} gives you additional supports for ```.yml```, ```.yaml``` and ```.toml``` file
|
|
973
|
+
*
|
|
974
|
+
* If file extension is ```.*```, it will be auto detected and parsed accordingly
|
|
975
|
+
*
|
|
976
|
+
* @method
|
|
977
|
+
* @async
|
|
978
|
+
* @param {string} file - File to read and parse
|
|
979
|
+
* @param {Object} [options={}] - Options
|
|
980
|
+
* @param {boolean} [options.ignoreError] - Any exception will be silently discarded
|
|
981
|
+
* @param {string} [options.ns] - If given, use this as the scope
|
|
982
|
+
* @param {string} [options.pattern] - If given and auto detection is on (extension is ```.*```), it will be used for instead the default one
|
|
983
|
+
* @param {Object} [options.globOptions={}] - {@link https://github.com/mrmlnc/fast-glob|fast-glob} options
|
|
984
|
+
* @param {Object} [options.defValue={}] - Default value to use if value returned empty
|
|
985
|
+
* @param {Object} [options.opts={}] - Parser setting
|
|
986
|
+
* @returns {Object}
|
|
987
|
+
*/
|
|
988
|
+
readConfig = async (file, { ns, pattern, globOptions = {}, ignoreError, defValue = {}, opts = {} } = {}) => {
|
|
989
|
+
if (!ns) ns = this.name
|
|
990
|
+
file = resolvePath(this.getPluginFile(file))
|
|
991
|
+
let ext = path.extname(file)
|
|
992
|
+
const fname = path.dirname(file) + '/' + path.basename(file, ext)
|
|
993
|
+
ext = ext.toLowerCase()
|
|
994
|
+
if (ext === '.js') {
|
|
995
|
+
const { readHandler } = find(this.app.bajo.configHandlers, { ext })
|
|
996
|
+
return this.parseObject(await readHandler.call(this.app[ns], file, opts))
|
|
997
|
+
}
|
|
998
|
+
if (ext === '.json') return await this.readJson(file)
|
|
999
|
+
if (!['', '.*'].includes(ext)) {
|
|
1000
|
+
const item = find(this.app.bajo.configHandlers, { ext })
|
|
1001
|
+
if (!item) {
|
|
1002
|
+
if (!ignoreError) throw this.error('cantParse%s', file, { code: 'BAJO_CONFIG_NO_PARSER' })
|
|
1003
|
+
return this.parseObject(defValue)
|
|
1004
|
+
}
|
|
1005
|
+
return this.parseObject(await item.readHandler.call(this.app[ns], file, opts))
|
|
1006
|
+
}
|
|
1007
|
+
const item = pattern ?? `${fname}.{${map(map(this.app.bajo.configHandlers, 'ext'), k => k.slice(1)).join(',')}}`
|
|
1008
|
+
const files = await fastGlob(item, globOptions)
|
|
1009
|
+
if (files.length === 0) {
|
|
1010
|
+
if (!ignoreError) throw this.error('noConfigFileFound', { code: 'BAJO_CONFIG_FILE_NOT_FOUND' })
|
|
1011
|
+
return this.parseObject(defValue)
|
|
1012
|
+
}
|
|
1013
|
+
let config = defValue
|
|
1014
|
+
for (const f of files) {
|
|
1015
|
+
const ext = path.extname(f).toLowerCase()
|
|
1016
|
+
const item = find(this.app.bajo.configHandlers, { ext })
|
|
1017
|
+
if (!item) {
|
|
1018
|
+
if (!ignoreError) throw this.error('cantParse%s', f, { code: 'BAJO_CONFIG_NO_PARSER' })
|
|
1019
|
+
continue
|
|
1020
|
+
}
|
|
1021
|
+
config = await item.readHandler.call(this.app[ns], f, opts)
|
|
1022
|
+
if (!isEmpty(config)) break
|
|
1023
|
+
}
|
|
1024
|
+
return this.parseObject(config)
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Read and parse json file
|
|
1029
|
+
*
|
|
1030
|
+
* @method
|
|
1031
|
+
* @param {string} file - File to read
|
|
1032
|
+
* @param {boolean} [thrownNotFound=false] - If ```true```, silently ignore if file is not found
|
|
1033
|
+
* @returns {Object}
|
|
1034
|
+
*/
|
|
1035
|
+
readJson = (file, thrownNotFound = false) => {
|
|
1036
|
+
if (isPlainObject(thrownNotFound)) thrownNotFound = false
|
|
1037
|
+
if (!fs.existsSync(file) && thrownNotFound) throw this.error('notFound%s%s', this.print.write('file'), file)
|
|
1038
|
+
let resp
|
|
1039
|
+
try {
|
|
1040
|
+
resp = fs.readFileSync(file, 'utf8')
|
|
1041
|
+
} catch (err) {}
|
|
1042
|
+
if (isEmpty(resp)) return resp
|
|
1043
|
+
return this.parseObject(JSON.parse(resp))
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Run named hook
|
|
1048
|
+
*
|
|
1049
|
+
* @method
|
|
1050
|
+
* @async
|
|
1051
|
+
* @param {string} hookName - ns based hook name
|
|
1052
|
+
* @param {...any} [args] - Argument passed to the hook function
|
|
1053
|
+
* @returns {Array} Array of hook execution results
|
|
1054
|
+
*/
|
|
1055
|
+
runHook = async (hookName, ...args) => {
|
|
1056
|
+
const [ns, path] = (hookName ?? '').split(':')
|
|
1057
|
+
let fns = filter(this.app.bajo.hooks, { ns, path })
|
|
1058
|
+
if (isEmpty(fns)) return []
|
|
1059
|
+
fns = orderBy(fns, ['level'])
|
|
1060
|
+
const results = []
|
|
1061
|
+
for (const i in fns) {
|
|
1062
|
+
const fn = fns[i]
|
|
1063
|
+
const scope = this.app[fn.src]
|
|
1064
|
+
const res = await fn.handler.call(scope, ...args)
|
|
1065
|
+
results.push({
|
|
1066
|
+
hook: hookName,
|
|
1067
|
+
resp: res
|
|
1068
|
+
})
|
|
1069
|
+
if (this.config.log.traceHook) scope.log.trace('hookExecuted%s', hookName)
|
|
1070
|
+
}
|
|
1071
|
+
return results
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Save item as file in Bajo's download directory. That is a directory inside your
|
|
1076
|
+
* Bajo plugin's data directory.
|
|
1077
|
+
*
|
|
1078
|
+
* If file exists already, file will automatically be
|
|
1079
|
+
* renamed incrementally.
|
|
1080
|
+
*
|
|
1081
|
+
* @method
|
|
1082
|
+
* @async
|
|
1083
|
+
* @param {string} file - File name
|
|
1084
|
+
* @param {Object} item - Item to save
|
|
1085
|
+
* @param {boolean} [printSaved=true] - Print info on screen
|
|
1086
|
+
* @returns {string} Full file path
|
|
1087
|
+
*/
|
|
1088
|
+
saveAsDownload = async (file, item, printSaved = true) => {
|
|
1089
|
+
const { print, getPluginDataDir } = this.app.bajo
|
|
1090
|
+
const fname = increment(`${getPluginDataDir(this.name)}/download/${trim(file, '/')}`, { fs: true })
|
|
1091
|
+
const dir = path.dirname(fname)
|
|
1092
|
+
if (!fs.existsSync(dir)) fs.ensureDirSync(dir)
|
|
1093
|
+
await fs.writeFile(fname, item, 'utf8')
|
|
1094
|
+
if (printSaved) print.succeed('savedAs%s', path.resolve(fname), { skipSilence: true })
|
|
1095
|
+
return fname
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
export default Bajo
|
|
1100
|
+
</code></pre></article></section></div></div></div><div class="search-container" id="PkfLWpAbet" style="display:none"><div class="wrapper" id="iCxFxjkHbP"><button class="icon-button search-close-button" id="VjLlGakifb" aria-label="close search"><svg><use xlink:href="#close-icon"></use></svg></button><div class="search-box-c"><svg><use xlink:href="#search-icon"></use></svg> <input type="text" id="vpcKVYIppa" class="search-input" placeholder="Search..." autofocus></div><div class="search-result-c" id="fWwVHRuDuN"><span class="search-result-c-text">Type anything to view search result</span></div></div></div><div class="mobile-menu-icon-container"><button class="icon-button" id="mobile-menu" data-isopen="false" aria-label="menu"><svg><use xlink:href="#menu-icon"></use></svg></button></div><div id="mobile-sidebar" class="mobile-sidebar-container"><div class="mobile-sidebar-wrapper"><a href="/" class="sidebar-title sidebar-title-anchor">Bajo Framework</a><div class="mobile-nav-links"><div class="navbar-item"><a id="" href="https://bajo.app" target="">Bajo.app</a></div><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/bajo" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/bajo" target="">Github</a></div></div><div class="mobile-sidebar-items-c"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-class_helper_bajo.html">class/helper/bajo</a></div><div class="sidebar-section-children"><a href="module-class_helper_plugin.html">class/helper/plugin</a></div><div class="sidebar-section-children"><a href="module-lib_create-method.html">lib/create-method</a></div><div class="sidebar-section-children"><a href="module-lib_formats.html">lib/formats</a></div><div class="sidebar-section-children"><a href="module-lib_log-levels.html">lib/log-levels</a></div><div class="sidebar-section-children"><a href="module-lib_resolve-path.html">lib/resolve-path</a></div><div class="sidebar-section-children"><a href="module-lib_shim.html">lib/shim</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="App.html">App</a></div><div class="sidebar-section-children"><a href="Bajo.html">Bajo</a></div><div class="sidebar-section-children"><a href="BasePlugin.html">BasePlugin</a></div><div class="sidebar-section-children"><a href="Err.html">Err</a></div><div class="sidebar-section-children"><a href="Log.html">Log</a></div><div class="sidebar-section-children"><a href="Plugin.html">Plugin</a></div><div class="sidebar-section-children"><a href="Print.html">Print</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-tutorials"><div>Tutorials</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="tutorial-contribution.html">Contribution</a></div><div class="sidebar-section-children"><a href="tutorial-ecosystem.html">Ecosystem</a></div><div class="sidebar-section-children"><a href="tutorial-getting-started.html">Getting Started</a></div><div class="sidebar-section-children"><a href="tutorial-plugin-dev.html">Plugin Development</a></div><div class="sidebar-section-children"><a href="tutorial-user-guide.html">User Guide</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#NsPathType">NsPathType</a></div><div class="sidebar-section-children"><a href="global.html#ObjectFormatType">ObjectFormatType</a></div></div></div><div class="mobile-navbar-actions"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div></div></div><script type="text/javascript" src="scripts/core.min.js"></script><script src="scripts/search.min.js" defer="defer"></script><script src="scripts/third-party/fuse.js" defer="defer"></script><script type="text/javascript">var tocbotInstance=tocbot.init({tocSelector:"#eed4d2a0bfd64539bb9df78095dec881",contentSelector:".main-content",headingSelector:"h1, h2, h3",hasInnerContainers:!0,scrollContainer:".main-content",headingsOffset:130,onClick:bringLinkToView})</script></body></html>
|