bajo 1.2.8 → 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.
Files changed (167) hide show
  1. package/.jsdoc.conf.json +6 -3
  2. package/README.md +8 -148
  3. package/class/app.js +113 -0
  4. package/{boot/class/bajo-core.js → class/bajo.js} +449 -37
  5. package/{boot/class/error.js → class/base/err.js} +29 -3
  6. package/class/base/log.js +205 -0
  7. package/class/base/plugin.js +177 -0
  8. package/class/base/print.js +272 -0
  9. package/class/helper/bajo.js +344 -0
  10. package/class/helper/plugin.js +169 -0
  11. package/{boot/class/bajo-plugin.js → class/plugin.js} +60 -3
  12. package/docs/App.html +3 -0
  13. package/docs/Bajo.html +15 -0
  14. package/docs/BasePlugin.html +5 -0
  15. package/docs/Err.html +3 -0
  16. package/docs/Log.html +3 -0
  17. package/docs/Plugin.html +3 -0
  18. package/docs/Print.html +3 -0
  19. package/docs/bitcoin.jpeg +0 -0
  20. package/docs/class_app.js.html +116 -0
  21. package/docs/class_bajo.js.html +1100 -0
  22. package/docs/class_base_err.js.html +99 -0
  23. package/docs/class_base_log.js.html +208 -0
  24. package/docs/class_base_plugin.js.html +180 -0
  25. package/docs/class_base_print.js.html +275 -0
  26. package/docs/class_helper_bajo.js.html +347 -0
  27. package/docs/class_helper_plugin.js.html +172 -0
  28. package/docs/class_plugin.js.html +121 -0
  29. package/docs/data/search.json +1 -0
  30. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  31. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  32. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  33. package/docs/global.html +3 -0
  34. package/docs/index.html +3 -0
  35. package/docs/lib_create-method.js.html +42 -0
  36. package/docs/lib_formats.js.html +68 -0
  37. package/docs/lib_log-levels.js.html +28 -0
  38. package/docs/lib_resolve-path.js.html +30 -0
  39. package/docs/lib_shim.js.html +35 -0
  40. package/docs/module-class_helper_bajo.html +3 -0
  41. package/docs/module-class_helper_plugin.html +3 -0
  42. package/docs/module-lib_create-method.html +3 -0
  43. package/docs/module-lib_formats.html +3 -0
  44. package/docs/module-lib_log-levels.html +3 -0
  45. package/docs/module-lib_resolve-path.html +3 -0
  46. package/docs/module-lib_shim.html +3 -0
  47. package/docs/scripts/core.js +726 -0
  48. package/docs/scripts/core.min.js +23 -0
  49. package/docs/scripts/resize.js +90 -0
  50. package/docs/scripts/search.js +265 -0
  51. package/docs/scripts/search.min.js +6 -0
  52. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  53. package/docs/scripts/third-party/fuse.js +9 -0
  54. package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
  55. package/docs/scripts/third-party/hljs-line-num.js +1 -0
  56. package/docs/scripts/third-party/hljs-original.js +5171 -0
  57. package/docs/scripts/third-party/hljs.js +1 -0
  58. package/docs/scripts/third-party/popper.js +5 -0
  59. package/docs/scripts/third-party/tippy.js +1 -0
  60. package/docs/scripts/third-party/tocbot.js +672 -0
  61. package/docs/scripts/third-party/tocbot.min.js +1 -0
  62. package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
  63. package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
  64. package/docs/styles/clean-jsdoc-theme-light.css +482 -0
  65. package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  66. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  67. package/docs/styles/clean-jsdoc-theme.min.css +1 -0
  68. package/docs/tutorial-contribution.html +3 -0
  69. package/docs/tutorial-ecosystem.html +3 -0
  70. package/docs/tutorial-getting-started.html +13 -0
  71. package/docs/tutorial-plugin-dev.html +3 -0
  72. package/docs/tutorial-user-guide.html +3 -0
  73. package/lib/create-method.js +39 -0
  74. package/{boot/lib → lib}/formats.js +28 -0
  75. package/lib/log-levels.js +25 -0
  76. package/{boot/lib → lib}/parse-args-argv.js +1 -1
  77. package/{boot/lib → lib}/resolve-path.js +12 -0
  78. package/{boot/lib → lib}/shim.js +10 -0
  79. package/misc-docs/bitcoin.jpeg +0 -0
  80. package/misc-docs/contribution.md +20 -0
  81. package/{docs → misc-docs}/ecosystem.md +41 -12
  82. package/misc-docs/getting-started.md +142 -0
  83. package/misc-docs/plugin-dev.md +0 -0
  84. package/misc-docs/toc.json +17 -0
  85. package/misc-docs/user-guide.md +1 -0
  86. package/package.json +14 -12
  87. package/bajoBook/book/doc/.metadata.json +0 -28
  88. package/bajoBook/book/doc/How-to-Make-a-Paper-Boat-564x400@2x.jpg +0 -0
  89. package/bajoBook/book/doc/pages/guides/definition.md +0 -7
  90. package/bajoBook/book/doc/pages/guides/intro.md +0 -3
  91. package/bajoBook/book/doc/pages/guides/setup.md +0 -22
  92. package/bajoBook/book/reference/.metadata.json +0 -152
  93. package/bajoBook/book/reference/concept-leadership-business-with-paper-boats.jpg +0 -0
  94. package/bajoBook/book/reference/pages/configuration/configuration-file.md +0 -52
  95. package/bajoBook/book/reference/pages/helper/break-ns-path.md +0 -24
  96. package/bajoBook/book/reference/pages/helper/build-collections.md +0 -19
  97. package/bajoBook/book/reference/pages/helper/call-helper-or-handler.md +0 -35
  98. package/bajoBook/book/reference/pages/helper/current-loc.md +0 -28
  99. package/bajoBook/book/reference/pages/helper/dayjs.md +0 -13
  100. package/bajoBook/book/reference/pages/helper/defaults-deep.md +0 -29
  101. package/bajoBook/book/reference/pages/helper/dump.md +0 -32
  102. package/bajoBook/book/reference/pages/helper/each-plugins.md +0 -24
  103. package/bajoBook/book/reference/pages/helper/envs.md +0 -11
  104. package/bajoBook/book/reference/pages/helper/error.md +0 -29
  105. package/bajoBook/book/reference/pages/helper/fatal.md +0 -18
  106. package/bajoBook/book/reference/pages/helper/freeze.md +0 -13
  107. package/bajoBook/book/reference/pages/helper/generate-id.md +0 -36
  108. package/bajoBook/book/reference/pages/helper/get-config.md +0 -27
  109. package/bajoBook/book/reference/pages/helper/get-global-module-dir.md +0 -13
  110. package/bajoBook/book/reference/pages/helper/get-helper.md +0 -13
  111. package/bajoBook/book/reference/pages/helper/get-item-by-name.md +0 -13
  112. package/bajoBook/book/reference/pages/helper/get-key-by-value.md +0 -13
  113. package/bajoBook/book/reference/pages/helper/get-module-dir.md +0 -13
  114. package/bajoBook/book/reference/pages/helper/get-plugin-data-dir.md +0 -13
  115. package/bajoBook/book/reference/pages/helper/get-plugin-name.md +0 -13
  116. package/bajoBook/book/reference/pages/helper/get-plugin.md +0 -13
  117. package/bajoBook/book/reference/pages/helper/import-module.md +0 -13
  118. package/bajoBook/book/reference/pages/helper/import-pkg.md +0 -13
  119. package/bajoBook/book/reference/pages/helper/is-empty-dir.md +0 -13
  120. package/bajoBook/book/reference/pages/helper/is-log-in-range.md +0 -13
  121. package/bajoBook/book/reference/pages/helper/is-set.md +0 -13
  122. package/bajoBook/book/reference/pages/helper/is-valid-app.md +0 -13
  123. package/bajoBook/book/reference/pages/helper/is-valid-plugin.md +0 -13
  124. package/bajoBook/book/reference/pages/helper/log-levels.md +0 -13
  125. package/bajoBook/book/reference/pages/helper/log.md +0 -13
  126. package/bajoBook/book/reference/pages/helper/paginate.md +0 -13
  127. package/bajoBook/book/reference/pages/helper/pascal-case.md +0 -13
  128. package/bajoBook/book/reference/pages/helper/print.md +0 -13
  129. package/bajoBook/book/reference/pages/helper/read-config.md +0 -13
  130. package/bajoBook/book/reference/pages/helper/read-json.md +0 -13
  131. package/bajoBook/book/reference/pages/helper/resolve-path.md +0 -13
  132. package/bajoBook/book/reference/pages/helper/resolve-tpl-path.md +0 -13
  133. package/bajoBook/book/reference/pages/helper/run-hook.md +0 -13
  134. package/bajoBook/book/reference/pages/helper/save-as-download.md +0 -13
  135. package/bajoBook/book/reference/pages/helper/titleize.md +0 -13
  136. package/bajoBook/book/reference/pages/helper/white-space.md +0 -13
  137. package/boot/class/app.js +0 -67
  138. package/boot/class/bajo-core/boot-order.js +0 -35
  139. package/boot/class/bajo-core/boot-plugins.js +0 -17
  140. package/boot/class/bajo-core/build-config.js +0 -85
  141. package/boot/class/bajo-core/build-plugins.js +0 -44
  142. package/boot/class/bajo-core/collect-config-handlers.js +0 -20
  143. package/boot/class/bajo-core/exit-handler.js +0 -53
  144. package/boot/class/bajo-core/run-as-applet.js +0 -26
  145. package/boot/class/bajo-plugin/attach-method.js +0 -14
  146. package/boot/class/bajo-plugin/build-config.js +0 -15
  147. package/boot/class/bajo-plugin/check-clash.js +0 -18
  148. package/boot/class/bajo-plugin/check-dependency.js +0 -39
  149. package/boot/class/bajo-plugin/collect-hooks.js +0 -31
  150. package/boot/class/bajo-plugin/run.js +0 -23
  151. package/boot/class/log.js +0 -90
  152. package/boot/class/plugin.js +0 -79
  153. package/boot/class/print.js +0 -153
  154. package/boot/lib/create-method.js +0 -33
  155. package/boot/lib/log-levels.js +0 -1
  156. package/test/method/isSet.js +0 -43
  157. /package/{bajo → extend/bajo}/intl/en-US.json +0 -0
  158. /package/{bajo → extend/bajo}/intl/id.json +0 -0
  159. /package/{waibuStatic → extend/waibuStatic}/virtual.json +0 -0
  160. /package/{boot/index.js → index.js} +0 -0
  161. /package/{boot/lib → lib}/current-loc.js +0 -0
  162. /package/{boot/lib → lib}/dayjs.js +0 -0
  163. /package/{boot/lib → lib}/import-module.js +0 -0
  164. /package/{boot/lib → lib}/omitted-plugin-keys.js +0 -0
  165. /package/{boot/lib → lib}/parse-env.js +0 -0
  166. /package/{boot/lib → lib}/read-all-configs.js +0 -0
  167. /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:///&lt;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 &amp; 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 &amp;&amp; item.skipOnTool &amp;&amp; !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:') &amp;&amp; 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) &amp;&amp; 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] === '_' &amp;&amp; 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) &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; this.app &amp;&amp; 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 &amp;&amp; !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' &amp;&amp; this &amp;&amp; this.app &amp;&amp; this.app[ns] &amp;&amp; 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 &amp;&amp; (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:&lt;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 &amp;&amp; has(mod, 'default')) {
757
+ mod = mod.default
758
+ if (opts.returnDefault &amp;&amp; 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 &amp;&amp; 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) &amp;&amp; !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 &amp; 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) &amp;&amp; v.startsWith('t:') &amp;&amp; lang) v = translate(v.slice(2))
936
+ try {
937
+ if (statics.includes(v)) obj[k] = v
938
+ else if (k.startsWith('t:') &amp;&amp; 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 &amp;&amp; !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) &amp;&amp; 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>