cyberia 3.1.3 → 3.2.9

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 (377) hide show
  1. package/.env.example +0 -2
  2. package/.github/workflows/engine-cyberia.cd.yml +10 -8
  3. package/.github/workflows/engine-cyberia.ci.yml +12 -29
  4. package/.github/workflows/ghpkg.ci.yml +4 -4
  5. package/.github/workflows/npmpkg.ci.yml +28 -11
  6. package/.github/workflows/publish.ci.yml +21 -2
  7. package/.github/workflows/pwa-microservices-template-page.cd.yml +4 -5
  8. package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
  9. package/.github/workflows/release.cd.yml +14 -10
  10. package/CHANGELOG.md +783 -1
  11. package/CLI-HELP.md +95 -18
  12. package/Dockerfile +0 -2
  13. package/README.md +290 -220
  14. package/bin/build.js +24 -7
  15. package/bin/cyberia.js +2838 -252
  16. package/bin/deploy.js +747 -125
  17. package/bin/file.js +9 -0
  18. package/bin/index.js +2838 -252
  19. package/bin/vs.js +1 -1
  20. package/conf.js +99 -65
  21. package/deployment.yaml +18 -164
  22. package/hardhat/hardhat.config.js +13 -13
  23. package/hardhat/ignition/modules/ObjectLayerToken.js +1 -1
  24. package/hardhat/package-lock.json +2559 -5864
  25. package/hardhat/package.json +14 -23
  26. package/hardhat/scripts/deployObjectLayerToken.js +1 -1
  27. package/hardhat/test/ObjectLayerToken.js +4 -2
  28. package/hardhat/types/ethers-contracts/ObjectLayerToken.ts +690 -0
  29. package/hardhat/types/ethers-contracts/common.ts +92 -0
  30. package/hardhat/types/ethers-contracts/factories/ObjectLayerToken__factory.ts +1055 -0
  31. package/hardhat/types/ethers-contracts/factories/index.ts +4 -0
  32. package/hardhat/types/ethers-contracts/hardhat.d.ts +47 -0
  33. package/hardhat/types/ethers-contracts/index.ts +6 -0
  34. package/jsconfig.json +1 -1
  35. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +6 -5
  36. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +6 -5
  37. package/manifests/deployment/dd-cyberia-development/deployment.yaml +18 -164
  38. package/manifests/deployment/dd-cyberia-development/proxy.yaml +7 -79
  39. package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
  40. package/manifests/deployment/dd-test-development/deployment.yaml +112 -28
  41. package/manifests/deployment/dd-test-development/proxy.yaml +46 -1
  42. package/manifests/deployment/playwright/deployment.yaml +1 -1
  43. package/nodemon.json +1 -1
  44. package/package.json +39 -24
  45. package/proxy.yaml +7 -79
  46. package/scripts/k3s-node-setup.sh +2 -2
  47. package/scripts/nat-iptables.sh +103 -18
  48. package/scripts/rhel-grpc-setup.sh +56 -0
  49. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +58 -14
  50. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +23 -14
  51. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  52. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +148 -20
  53. package/src/api/core/core.controller.js +10 -10
  54. package/src/api/core/core.service.js +10 -10
  55. package/src/api/crypto/crypto.controller.js +8 -8
  56. package/src/api/crypto/crypto.service.js +8 -8
  57. package/src/api/cyberia-action/cyberia-action.controller.js +74 -0
  58. package/src/api/cyberia-action/cyberia-action.model.js +87 -0
  59. package/src/api/cyberia-action/cyberia-action.router.js +27 -0
  60. package/src/api/cyberia-action/cyberia-action.service.js +42 -0
  61. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +93 -0
  62. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +36 -0
  63. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +29 -0
  64. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +51 -0
  65. package/src/api/cyberia-entity/cyberia-entity.controller.js +74 -0
  66. package/src/api/cyberia-entity/cyberia-entity.model.js +24 -0
  67. package/src/api/cyberia-entity/cyberia-entity.router.js +27 -0
  68. package/src/api/cyberia-entity/cyberia-entity.service.js +42 -0
  69. package/src/api/cyberia-instance/cyberia-fallback-world.js +178 -0
  70. package/src/api/cyberia-instance/cyberia-instance.controller.js +92 -0
  71. package/src/api/cyberia-instance/cyberia-instance.model.js +87 -0
  72. package/src/api/cyberia-instance/cyberia-instance.router.js +63 -0
  73. package/src/api/cyberia-instance/cyberia-instance.service.js +156 -0
  74. package/src/api/cyberia-instance/cyberia-portal-connector.js +260 -0
  75. package/src/api/cyberia-instance/cyberia-world-generator.js +505 -0
  76. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +74 -0
  77. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +574 -0
  78. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +231 -0
  79. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +27 -0
  80. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +46 -0
  81. package/src/api/cyberia-map/cyberia-map.controller.js +79 -0
  82. package/src/api/cyberia-map/cyberia-map.model.js +30 -0
  83. package/src/api/cyberia-map/cyberia-map.router.js +40 -0
  84. package/src/api/cyberia-map/cyberia-map.service.js +74 -0
  85. package/src/api/cyberia-quest/cyberia-quest.controller.js +74 -0
  86. package/src/api/cyberia-quest/cyberia-quest.model.js +67 -0
  87. package/src/api/cyberia-quest/cyberia-quest.router.js +27 -0
  88. package/src/api/cyberia-quest/cyberia-quest.service.js +42 -0
  89. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +74 -0
  90. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +49 -0
  91. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +27 -0
  92. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +42 -0
  93. package/src/api/default/default.controller.js +10 -10
  94. package/src/api/default/default.service.js +10 -10
  95. package/src/api/document/document.controller.js +12 -12
  96. package/src/api/document/document.model.js +10 -16
  97. package/src/api/file/file.controller.js +8 -8
  98. package/src/api/file/file.model.js +10 -10
  99. package/src/api/file/file.ref.json +18 -0
  100. package/src/api/file/file.service.js +36 -36
  101. package/src/api/instance/instance.controller.js +10 -10
  102. package/src/api/instance/instance.model.js +4 -10
  103. package/src/api/instance/instance.service.js +10 -10
  104. package/src/api/ipfs/ipfs.controller.js +15 -36
  105. package/src/api/ipfs/ipfs.model.js +47 -47
  106. package/src/api/ipfs/ipfs.router.js +8 -13
  107. package/src/api/ipfs/ipfs.service.js +67 -129
  108. package/src/api/object-layer/object-layer.controller.js +12 -12
  109. package/src/api/object-layer/object-layer.model.js +4 -17
  110. package/src/api/object-layer/object-layer.router.js +30 -0
  111. package/src/api/object-layer/object-layer.service.js +126 -43
  112. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +10 -10
  113. package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +6 -16
  114. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +18 -14
  115. package/src/api/test/test.controller.js +8 -8
  116. package/src/api/test/test.service.js +8 -8
  117. package/src/api/user/guest.service.js +99 -0
  118. package/src/api/user/user.controller.js +6 -6
  119. package/src/api/user/user.model.js +8 -13
  120. package/src/api/user/user.service.js +11 -27
  121. package/src/cli/cluster.js +68 -21
  122. package/src/cli/db.js +753 -825
  123. package/src/cli/deploy.js +215 -125
  124. package/src/cli/env.js +29 -0
  125. package/src/cli/fs.js +82 -8
  126. package/src/cli/image.js +43 -1
  127. package/src/cli/index.js +74 -3
  128. package/src/cli/kubectl.js +211 -0
  129. package/src/cli/release.js +340 -0
  130. package/src/cli/repository.js +475 -74
  131. package/src/cli/run.js +582 -43
  132. package/src/cli/secrets.js +73 -0
  133. package/src/cli/ssh.js +1 -1
  134. package/src/cli/static.js +43 -115
  135. package/src/cli/test.js +3 -3
  136. package/src/client/Cryptokoyn.index.js +18 -22
  137. package/src/client/CyberiaPortal.index.js +19 -24
  138. package/src/client/Default.index.js +21 -34
  139. package/src/client/Itemledger.index.js +20 -27
  140. package/src/client/Underpost.index.js +19 -24
  141. package/src/client/components/core/404.js +4 -4
  142. package/src/client/components/core/500.js +4 -4
  143. package/src/client/components/core/Account.js +73 -60
  144. package/src/client/components/core/AgGrid.js +23 -33
  145. package/src/client/components/core/Alert.js +12 -13
  146. package/src/client/components/core/AppStore.js +69 -0
  147. package/src/client/components/core/Auth.js +35 -37
  148. package/src/client/components/core/Badge.js +7 -13
  149. package/src/client/components/core/BtnIcon.js +15 -17
  150. package/src/client/components/core/CalendarCore.js +43 -64
  151. package/src/client/components/core/Chat.js +13 -15
  152. package/src/client/components/core/ClientEvents.js +87 -0
  153. package/src/client/components/core/ColorPaletteElement.js +309 -0
  154. package/src/client/components/core/Content.js +17 -14
  155. package/src/client/components/core/Css.js +15 -71
  156. package/src/client/components/core/CssCore.js +12 -16
  157. package/src/client/components/core/D3Chart.js +4 -4
  158. package/src/client/components/core/Docs.js +64 -91
  159. package/src/client/components/core/DropDown.js +194 -96
  160. package/src/client/components/core/EventBus.js +92 -0
  161. package/src/client/components/core/EventsUI.js +14 -17
  162. package/src/client/components/core/FileExplorer.js +96 -228
  163. package/src/client/components/core/FullScreen.js +47 -75
  164. package/src/client/components/core/Input.js +24 -69
  165. package/src/client/components/core/Keyboard.js +26 -19
  166. package/src/client/components/core/KeyboardAvoidance.js +145 -0
  167. package/src/client/components/core/LoadingAnimation.js +25 -31
  168. package/src/client/components/core/LogIn.js +43 -43
  169. package/src/client/components/core/LogOut.js +25 -16
  170. package/src/client/components/core/Modal.js +462 -179
  171. package/src/client/components/core/NotificationManager.js +14 -18
  172. package/src/client/components/core/Panel.js +54 -51
  173. package/src/client/components/core/PanelForm.js +44 -144
  174. package/src/client/components/core/Polyhedron.js +110 -214
  175. package/src/client/components/core/PublicProfile.js +39 -32
  176. package/src/client/components/core/Recover.js +48 -44
  177. package/src/client/components/core/Responsive.js +88 -32
  178. package/src/client/components/core/RichText.js +9 -18
  179. package/src/client/components/core/Router.js +24 -3
  180. package/src/client/components/core/SearchBox.js +37 -37
  181. package/src/client/components/core/SignUp.js +39 -30
  182. package/src/client/components/core/SocketIo.js +112 -30
  183. package/src/client/components/core/SocketIoHandler.js +75 -0
  184. package/src/client/components/core/Stream.js +143 -95
  185. package/src/client/components/core/ToggleSwitch.js +8 -20
  186. package/src/client/components/core/ToolTip.js +5 -17
  187. package/src/client/components/core/Translate.js +56 -59
  188. package/src/client/components/core/Validator.js +26 -16
  189. package/src/client/components/core/Wallet.js +15 -26
  190. package/src/client/components/core/Webhook.js +40 -7
  191. package/src/client/components/core/Worker.js +163 -27
  192. package/src/client/components/core/windowGetDimensions.js +7 -7
  193. package/src/client/components/cryptokoyn/{MenuCryptokoyn.js → AppShellCryptokoyn.js} +59 -59
  194. package/src/client/components/cryptokoyn/AppStoreCryptokoyn.js +5 -0
  195. package/src/client/components/cryptokoyn/CssCryptokoyn.js +15 -15
  196. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +9 -7
  197. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +8 -6
  198. package/src/client/components/cryptokoyn/RouterCryptokoyn.js +37 -0
  199. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +4 -4
  200. package/src/client/components/cryptokoyn/SignUpCryptokoyn.js +6 -4
  201. package/src/client/components/cryptokoyn/SocketIoCryptokoyn.js +3 -51
  202. package/src/client/components/cyberia/InstanceEngineCyberia.js +781 -0
  203. package/src/client/components/cyberia/MapEngineCyberia.js +1836 -2
  204. package/src/client/components/cyberia/ObjectLayerEngine.js +19 -0
  205. package/src/client/components/cyberia/ObjectLayerEngineModal.js +1220 -99
  206. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +252 -316
  207. package/src/client/components/cyberia-portal/{MenuCyberiaPortal.js → AppShellCyberiaPortal.js} +136 -103
  208. package/src/client/components/cyberia-portal/AppStoreCyberiaPortal.js +5 -0
  209. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +462 -32
  210. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +15 -15
  211. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +9 -7
  212. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +8 -6
  213. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +4 -4
  214. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +60 -0
  215. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +4 -4
  216. package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +6 -4
  217. package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +3 -49
  218. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -4
  219. package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +91 -91
  220. package/src/client/components/default/AppStoreDefault.js +5 -0
  221. package/src/client/components/default/CssDefault.js +12 -12
  222. package/src/client/components/default/LogInDefault.js +9 -7
  223. package/src/client/components/default/LogOutDefault.js +8 -6
  224. package/src/client/components/default/RouterDefault.js +47 -0
  225. package/src/client/components/default/SettingsDefault.js +4 -4
  226. package/src/client/components/default/SignUpDefault.js +6 -4
  227. package/src/client/components/default/SocketIoDefault.js +3 -51
  228. package/src/client/components/default/TranslateDefault.js +3 -3
  229. package/src/client/components/itemledger/{MenuItemledger.js → AppShellItemledger.js} +59 -59
  230. package/src/client/components/itemledger/AppStoreItemledger.js +5 -0
  231. package/src/client/components/itemledger/CssItemledger.js +15 -15
  232. package/src/client/components/itemledger/LogInItemledger.js +9 -7
  233. package/src/client/components/itemledger/LogOutItemledger.js +8 -6
  234. package/src/client/components/itemledger/RouterItemledger.js +38 -0
  235. package/src/client/components/itemledger/SettingsItemledger.js +4 -4
  236. package/src/client/components/itemledger/SignUpItemledger.js +6 -4
  237. package/src/client/components/itemledger/SocketIoItemledger.js +3 -51
  238. package/src/client/components/itemledger/TranslateItemledger.js +3 -3
  239. package/src/client/components/underpost/{MenuUnderpost.js → AppShellUnderpost.js} +92 -92
  240. package/src/client/components/underpost/AppStoreUnderpost.js +5 -0
  241. package/src/client/components/underpost/CssUnderpost.js +14 -14
  242. package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +4 -4
  243. package/src/client/components/underpost/DocumentSearchProvider.js +1 -1
  244. package/src/client/components/underpost/LabGalleryUnderpost.js +12 -15
  245. package/src/client/components/underpost/LogInUnderpost.js +9 -7
  246. package/src/client/components/underpost/LogOutUnderpost.js +8 -6
  247. package/src/client/components/underpost/RouterUnderpost.js +45 -0
  248. package/src/client/components/underpost/SettingsUnderpost.js +4 -4
  249. package/src/client/components/underpost/SignUpUnderpost.js +6 -4
  250. package/src/client/components/underpost/SocketIoUnderpost.js +3 -51
  251. package/src/client/components/underpost/TranslateUnderpost.js +4 -4
  252. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +235 -0
  253. package/src/client/public/cyberia-docs/ARCHITECTURE.md +443 -0
  254. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +417 -0
  255. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +313 -0
  256. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +260 -0
  257. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +241 -0
  258. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +300 -0
  259. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +279 -0
  260. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +206 -0
  261. package/src/client/public/cyberia-docs/ROADMAP.md +240 -0
  262. package/src/client/public/cyberia-docs/WHITE-PAPER.md +732 -0
  263. package/src/client/services/atlas-sprite-sheet/atlas-sprite-sheet.service.js +14 -20
  264. package/src/client/services/core/core.service.js +35 -55
  265. package/src/client/services/crypto/crypto.service.js +8 -13
  266. package/src/client/services/cyberia-action/cyberia-action.service.js +99 -0
  267. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +99 -0
  268. package/src/client/services/cyberia-entity/cyberia-entity.management.js +57 -0
  269. package/src/client/services/cyberia-entity/cyberia-entity.service.js +99 -0
  270. package/src/client/services/cyberia-instance/cyberia-instance.management.js +194 -0
  271. package/src/client/services/cyberia-instance/cyberia-instance.service.js +116 -0
  272. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +99 -0
  273. package/src/client/services/cyberia-map/cyberia-map.management.js +193 -0
  274. package/src/client/services/cyberia-map/cyberia-map.service.js +120 -0
  275. package/src/client/services/cyberia-quest/cyberia-quest.service.js +99 -0
  276. package/src/client/services/cyberia-quest-progress/cyberia-quest-progress.service.js +99 -0
  277. package/src/client/services/default/default.management.js +159 -267
  278. package/src/client/services/default/default.service.js +10 -16
  279. package/src/client/services/document/document.service.js +14 -19
  280. package/src/client/services/file/file.service.js +8 -13
  281. package/src/client/services/instance/instance.management.js +6 -6
  282. package/src/client/services/instance/instance.service.js +10 -15
  283. package/src/client/services/ipfs/ipfs.service.js +14 -40
  284. package/src/client/services/object-layer/object-layer.management.js +14 -14
  285. package/src/client/services/object-layer/object-layer.service.js +39 -24
  286. package/src/client/services/object-layer-render-frames/object-layer-render-frames.service.js +10 -16
  287. package/src/client/services/test/test.service.js +8 -13
  288. package/src/client/services/user/guest.service.js +86 -0
  289. package/src/client/services/user/user.management.js +6 -6
  290. package/src/client/services/user/user.service.js +14 -20
  291. package/src/client/ssr/body/404.js +3 -3
  292. package/src/client/ssr/body/500.js +3 -3
  293. package/src/client/ssr/body/CacheControl.js +5 -2
  294. package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
  295. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +13 -6
  296. package/src/client/ssr/head/PwaItemledger.js +197 -60
  297. package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
  298. package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
  299. package/src/client/ssr/offline/Maintenance.js +12 -11
  300. package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
  301. package/src/client/ssr/pages/CyberiaServerMetrics.js +1 -1
  302. package/src/client/ssr/pages/Test.js +2 -2
  303. package/src/client/sw/core.sw.js +212 -0
  304. package/src/grpc/cyberia/grpc-server.js +642 -0
  305. package/src/index.js +24 -1
  306. package/src/runtime/cyberia-client/Dockerfile +80 -0
  307. package/src/runtime/cyberia-server/Dockerfile +37 -0
  308. package/src/runtime/express/Dockerfile +5 -1
  309. package/src/runtime/express/Express.js +18 -1
  310. package/src/runtime/lampp/Dockerfile +17 -5
  311. package/src/runtime/lampp/Lampp.js +27 -4
  312. package/src/runtime/wp/Dockerfile +62 -0
  313. package/src/runtime/wp/Wp.js +639 -0
  314. package/src/server/atlas-sprite-sheet-generator.js +4 -2
  315. package/src/server/auth.js +24 -1
  316. package/src/server/backup.js +37 -9
  317. package/src/server/client-build-docs.js +52 -46
  318. package/src/server/client-build.js +356 -82
  319. package/src/server/client-formatted.js +140 -57
  320. package/src/server/conf.js +29 -13
  321. package/src/server/cron.js +25 -23
  322. package/src/server/data-query.js +32 -20
  323. package/src/server/dns.js +24 -1
  324. package/src/server/ipfs-client.js +253 -89
  325. package/src/server/object-layer.js +150 -114
  326. package/src/server/peer.js +8 -0
  327. package/src/server/process.js +13 -27
  328. package/src/server/runtime.js +25 -1
  329. package/src/server/semantic-layer-generator-floor.js +319 -0
  330. package/src/server/semantic-layer-generator-resource.js +259 -0
  331. package/src/server/semantic-layer-generator-skin.js +1164 -0
  332. package/src/server/semantic-layer-generator.js +211 -542
  333. package/src/server/shape-generator.js +108 -0
  334. package/src/server/start.js +19 -5
  335. package/src/server/valkey.js +141 -235
  336. package/src/ws/IoInterface.js +1 -10
  337. package/src/ws/IoServer.js +14 -33
  338. package/src/ws/core/channels/core.ws.chat.js +65 -20
  339. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  340. package/src/ws/core/channels/core.ws.stream.js +90 -31
  341. package/src/ws/core/core.ws.connection.js +12 -33
  342. package/src/ws/core/core.ws.emit.js +10 -26
  343. package/src/ws/core/core.ws.server.js +25 -58
  344. package/src/ws/default/channels/default.ws.main.js +53 -12
  345. package/src/ws/default/default.ws.connection.js +26 -13
  346. package/src/ws/default/default.ws.server.js +30 -12
  347. package/tsconfig.docs.json +15 -0
  348. package/typedoc.dd-cyberia.json +29 -0
  349. package/typedoc.json +29 -0
  350. package/WHITE-PAPER.md +0 -1540
  351. package/hardhat/README.md +0 -531
  352. package/hardhat/WHITE-PAPER.md +0 -1540
  353. package/jsdoc.dd-cyberia.json +0 -59
  354. package/jsdoc.json +0 -59
  355. package/src/api/object-layer/README.md +0 -347
  356. package/src/client/components/core/ColorPalette.js +0 -5267
  357. package/src/client/components/core/JoyStick.js +0 -80
  358. package/src/client/components/cryptokoyn/CommonCryptokoyn.js +0 -29
  359. package/src/client/components/cryptokoyn/ElementsCryptokoyn.js +0 -38
  360. package/src/client/components/cryptokoyn/RoutesCryptokoyn.js +0 -39
  361. package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +0 -38
  362. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +0 -58
  363. package/src/client/components/cyberia-portal/ServerCyberiaPortal.js +0 -136
  364. package/src/client/components/default/ElementsDefault.js +0 -38
  365. package/src/client/components/default/RoutesDefault.js +0 -49
  366. package/src/client/components/itemledger/CommonItemledger.js +0 -29
  367. package/src/client/components/itemledger/ElementsItemledger.js +0 -38
  368. package/src/client/components/itemledger/RoutesItemledger.js +0 -40
  369. package/src/client/components/underpost/CommonUnderpost.js +0 -29
  370. package/src/client/components/underpost/ElementsUnderpost.js +0 -38
  371. package/src/client/components/underpost/RoutesUnderpost.js +0 -47
  372. package/src/client/sw/default.sw.js +0 -127
  373. package/src/client/sw/template.sw.js +0 -84
  374. package/src/ws/core/management/core.ws.chat.js +0 -8
  375. package/src/ws/core/management/core.ws.mailer.js +0 -16
  376. package/src/ws/core/management/core.ws.stream.js +0 -8
  377. package/src/ws/default/management/default.ws.main.js +0 -8
package/src/cli/run.js CHANGED
@@ -4,7 +4,8 @@
4
4
  * @namespace UnderpostRun
5
5
  */
6
6
 
7
- import { daemonProcess, getTerminalPid, shellCd, shellExec } from '../server/process.js';
7
+ import { daemonProcess, getTerminalPid, pbcopy, shellCd, shellExec } from '../server/process.js';
8
+ import crypto from 'crypto';
8
9
  import {
9
10
  awaitDeployMonitor,
10
11
  buildKindPorts,
@@ -17,12 +18,31 @@ import {
17
18
  import { actionInitLog, loggerFactory } from '../server/logger.js';
18
19
 
19
20
  import fs from 'fs-extra';
21
+ import net from 'net';
20
22
  import { range, setPad, timer } from '../client/components/core/CommonJs.js';
21
23
 
22
24
  import os from 'os';
23
25
  import Underpost from '../index.js';
24
26
  import dotenv from 'dotenv';
25
27
 
28
+ const waitForPort = (port, host = '127.0.0.1', { maxAttempts = 30, interval = 2000 } = {}) =>
29
+ new Promise((resolve, reject) => {
30
+ let attempts = 0;
31
+ const tryConnect = () => {
32
+ attempts++;
33
+ const socket = net.createConnection({ port, host }, () => {
34
+ socket.destroy();
35
+ resolve();
36
+ });
37
+ socket.on('error', () => {
38
+ socket.destroy();
39
+ if (attempts >= maxAttempts) return reject(new Error(`Port ${port} not ready after ${maxAttempts} attempts`));
40
+ setTimeout(tryConnect, interval);
41
+ });
42
+ };
43
+ tryConnect();
44
+ });
45
+
26
46
  const logger = loggerFactory(import.meta);
27
47
 
28
48
  /**
@@ -73,7 +93,6 @@ const logger = loggerFactory(import.meta);
73
93
  * @property {boolean} kubeadm - Whether to run in kubeadm mode.
74
94
  * @property {boolean} kind - Whether to run in kind mode.
75
95
  * @property {boolean} k3s - Whether to run in k3s mode.
76
- * @property {string} logType - The type of log to generate.
77
96
  * @property {string} hosts - The hosts to use.
78
97
  * @property {string} deployId - The deployment ID.
79
98
  * @property {string} instanceId - The instance ID.
@@ -91,6 +110,10 @@ const logger = loggerFactory(import.meta);
91
110
  * @property {string|Array<{ip: string, hostnames: string[]}>} hostAliases - Adds entries to the Pod /etc/hosts via Kubernetes hostAliases.
92
111
  * As a string (CLI): semicolon-separated entries of "ip=hostname1,hostname2" (e.g., "127.0.0.1=foo.local,bar.local;10.1.2.3=foo.remote").
93
112
  * As an array (programmatic): objects with `ip` and `hostnames` fields (e.g., [{ ip: "127.0.0.1", hostnames: ["foo.local"] }]).
113
+ * @property {boolean} gitClean - Whether to perform a `git clean` before running.
114
+ * @property {boolean} copy - Whether to copy the command to the clipboard instead of executing it.
115
+ * @property {boolean} skipFullBuild - Whether to skip the full client bundle build during deployment (supported by: sync, template-deploy).
116
+ * @property {boolean} pullBundle - Whether to pull the bundle before running. Use together with --skip-full-build to skip the local build entirely (supported by: sync, template-deploy).
94
117
  * @memberof UnderpostRun
95
118
  */
96
119
  const DEFAULT_OPTION = {
@@ -138,7 +161,6 @@ const DEFAULT_OPTION = {
138
161
  kubeadm: false,
139
162
  kind: false,
140
163
  k3s: false,
141
- logType: '',
142
164
  hosts: '',
143
165
  deployId: '',
144
166
  instanceId: '',
@@ -154,6 +176,10 @@ const DEFAULT_OPTION = {
154
176
  createJobNow: false,
155
177
  fromNCommit: 0,
156
178
  hostAliases: '',
179
+ gitClean: false,
180
+ copy: false,
181
+ skipFullBuild: false,
182
+ pullBundle: false,
157
183
  };
158
184
 
159
185
  /**
@@ -246,8 +272,15 @@ class UnderpostRun {
246
272
  const ports = '6379,27017';
247
273
  shellExec(`node bin run kill '${ports}'`);
248
274
  shellExec(`node bin run dev-cluster --dev --expose --namespace ${options.namespace}`, { async: true });
249
- console.log('Loading fordward services...');
250
- await timer(5000);
275
+ logger.info('Waiting for port-forward services to be ready...');
276
+ try {
277
+ await Promise.all([waitForPort(27017), waitForPort(6379)]);
278
+ logger.info('Port-forward services are ready');
279
+ } catch (err) {
280
+ logger.error('Port-forward services failed to become ready', { error: err.message });
281
+ shellExec(`node bin run kill '${ports}'`);
282
+ throw err;
283
+ }
251
284
  shellExec(`node bin metadata --generate ${path}`);
252
285
  shellExec(`node bin db --dev --clean-fs-collection dd`);
253
286
  shellExec(`node bin run kill '${ports}'`);
@@ -373,8 +406,12 @@ class UnderpostRun {
373
406
  }
374
407
  shellExec(`${baseCommand} run pull`);
375
408
 
376
- // Capture last N commit messages for propagation (default: last 1 commit)
377
- const fromN = options.fromNCommit && parseInt(options.fromNCommit) > 0 ? parseInt(options.fromNCommit) : 1;
409
+ // Capture last N commit messages for propagation.
410
+ // When --from-n-commit is not set, auto-detect unpushed commit count (same as --unpush flag).
411
+ const fromN =
412
+ options.fromNCommit && parseInt(options.fromNCommit) > 0
413
+ ? parseInt(options.fromNCommit)
414
+ : Underpost.repo.getUnpushedCount('.').count;
378
415
  const message = shellExec(`node bin cmt --changelog ${fromN} --changelog-no-hash`, {
379
416
  silent: true,
380
417
  stdout: true,
@@ -404,6 +441,15 @@ class UnderpostRun {
404
441
  deployType = 'init';
405
442
  }
406
443
 
444
+ // If --build is set and path is a sync-engine-* target, push the pre-built client bundle
445
+ // to Cloudinary so the remote container can pull it instead of rebuilding from source.
446
+ if (options.build && deployConfId && deployConfId.startsWith('engine-')) {
447
+ const confName = deployConfId.replace(/^engine-/, '');
448
+ const pushDeployId = options.deployId || `dd-${confName}`;
449
+ logger.info(`[template-deploy] Running push-bundle for deployId: ${pushDeployId}`);
450
+ shellExec(`${baseCommand} run push-bundle --deploy-id ${pushDeployId}`);
451
+ }
452
+
407
453
  // Dispatch npmpkg CI workflow — this builds pwa-microservices-template first.
408
454
  // If deployConfId is set, npmpkg.ci.yml will dispatch the engine-<conf-id> CI
409
455
  // with sync=true after template build completes. The engine CI then dispatches
@@ -424,16 +470,50 @@ class UnderpostRun {
424
470
  },
425
471
 
426
472
  /**
427
- * @method template-deploy-image
428
- * @description Dispatches the Docker image CI workflow for the `engine` repository.
473
+ * @method template-deploy-local
474
+ * @description Similar to `template-deploy` but runs the workflow locally without dispatching GitHub Actions. It pulls the latest changes, pushes to GitHub, builds the template, and optionally triggers a local release with CI push.
475
+ * @param {string} path - The deployment path identifier (e.g., 'sync-engine-core', 'init-engine-core', or empty for build-only).
476
+ * @param {Object} options - The default underpost runner options for customizing workflow
477
+ * @memberof UnderpostRun
478
+ */
479
+ 'template-deploy-local': async (path, options = DEFAULT_OPTION) => {
480
+ const baseCommand = options.dev ? 'node bin' : 'underpost';
481
+ shellExec(`npm run security:secrets`);
482
+ const reportPath = './gitleaks-report.json';
483
+ if (fs.existsSync(reportPath) && JSON.parse(fs.readFileSync(reportPath, 'utf8')).length > 0) {
484
+ logger.error('Secrets detected in gitleaks-report.json, aborting template-deploy');
485
+ return;
486
+ }
487
+ shellExec(`${baseCommand} run pull`);
488
+
489
+ // Capture last N commit messages from the engine repo.
490
+ // When --from-n-commit is not set, auto-detect unpushed commit count (same as --unpush flag).
491
+ const fromN =
492
+ options.fromNCommit && parseInt(options.fromNCommit) > 0
493
+ ? parseInt(options.fromNCommit)
494
+ : Underpost.repo.getUnpushedCount('.').count;
495
+ const rawMessage = shellExec(`node bin cmt --changelog ${fromN} --changelog-no-hash`, {
496
+ silent: true,
497
+ stdout: true,
498
+ }).trim();
499
+ const sanitizedMessage = Underpost.repo.sanitizeChangelogMessage(rawMessage);
500
+
501
+ const { triggerCmd } = path
502
+ ? await Underpost.release.ci(path, sanitizedMessage, options)
503
+ : await Underpost.release.pwa(sanitizedMessage, options);
504
+ pbcopy(triggerCmd + ' && cd /home/dd/engine');
505
+ },
506
+ /**
507
+ * @method docker-image
508
+ * @description Dispatches the Docker image CI workflow (`docker-image.ci.yml`) for the `engine` repository via `workflow_dispatch`.
429
509
  * @param {string} path - The input value, identifier, or path for the operation.
430
510
  * @param {Object} options - The default underpost runner options for customizing workflow
431
511
  * @memberof UnderpostRun
432
512
  */
433
- 'template-deploy-image': (path, options = DEFAULT_OPTION) => {
513
+ 'docker-image': (path, options = DEFAULT_OPTION) => {
434
514
  Underpost.repo.dispatchWorkflow({
435
515
  repo: `${process.env.GITHUB_USERNAME}/engine`,
436
- workflowFile: 'docker-image.ci.yml',
516
+ workflowFile: `docker-image${path ? `.${path}` : ''}.ci.yml`,
437
517
  ref: 'master',
438
518
  inputs: {},
439
519
  });
@@ -570,8 +650,9 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
570
650
  sync: async (path, options = DEFAULT_OPTION) => {
571
651
  // Dev usage: node bin run --dev --build sync dd-default
572
652
  const env = options.dev ? 'development' : 'production';
573
- const baseCommand = options.dev ? 'node bin' : 'underpost';
653
+ const baseCommand = 'node bin'; // options.dev ? 'node bin' : 'underpost';
574
654
  const baseClusterCommand = options.dev ? ' --dev' : '';
655
+ const clusterFlag = options.k3s ? ' --k3s' : options.kind ? ' --kind' : ' --kubeadm';
575
656
  const defaultPath = [
576
657
  'dd-default',
577
658
  options.replicas,
@@ -586,13 +667,23 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
586
667
  image = image ? image : defaultPath[3];
587
668
  node = node ? node : defaultPath[4];
588
669
  shellExec(`${baseCommand} cluster --ns-use ${options.namespace}`);
670
+
671
+ if (image && !image.startsWith('localhost'))
672
+ Underpost.image.pullDockerHubImage({
673
+ dockerhubImage: image,
674
+ kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
675
+ kubeadm: options.nodeName || options.kubeadm,
676
+ k3s: options.k3s,
677
+ });
678
+
589
679
  if (isDeployRunnerContext(path, options)) {
590
680
  if (!options.disablePrivateConfUpdate) {
591
681
  const { validVersion } = Underpost.repo.privateConfUpdate(deployId);
592
682
  if (!validVersion) throw new Error('Version mismatch');
593
683
  }
594
684
  if (options.timezone !== 'none') shellExec(`${baseCommand} run${baseClusterCommand} tz`);
595
- if (options.deployIdCronJobs !== 'none') shellExec(`node bin cron --dev --setup-start --apply`);
685
+ if (options.deployIdCronJobs !== 'none')
686
+ shellExec(`node bin cron${baseClusterCommand}${clusterFlag} --setup-start --git --apply`);
596
687
  }
597
688
 
598
689
  const currentTraffic = isDeployRunnerContext(path, options)
@@ -605,20 +696,28 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
605
696
  const cmdString = options.cmd
606
697
  ? ' --cmd ' + (options.cmd.find((c) => c.match('"')) ? '"' + options.cmd + '"' : "'" + options.cmd + "'")
607
698
  : '';
699
+ const gitCleanFlag = options.gitClean ? ' --git-clean' : '';
700
+
701
+ const skipFullBuildFlag = options.skipFullBuild ? ' --skip-full-build' : '';
702
+ const pullBundleFlag = options.pullBundle ? ' --pull-bundle' : '';
608
703
 
609
704
  shellExec(
610
- `${baseCommand} deploy --kubeadm --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
705
+ `${baseCommand} deploy${clusterFlag} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
611
706
  image ? ` --image ${image}` : ''
612
707
  }${versions ? ` --versions ${versions}` : ''}${
613
708
  options.namespace ? ` --namespace ${options.namespace}` : ''
614
- }${timeoutFlags}${cmdString} ${deployId} ${env}`,
709
+ }${timeoutFlags}${cmdString}${gitCleanFlag}${skipFullBuildFlag}${pullBundleFlag} ${deployId} ${env}`,
615
710
  );
616
711
 
617
712
  if (isDeployRunnerContext(path, options)) {
713
+ // Backup app/services repositories with repo-backup configured
618
714
  shellExec(
619
- `${baseCommand} deploy --kubeadm${cmdString} --replicas ${replicas} --disable-update-proxy ${deployId} ${env} --versions ${versions}${
715
+ `${baseCommand} db ${deployId} ${clusterFlag}${baseClusterCommand} --repo-backup --primary-pod --git --force-clone --preserveUUID ${options.namespace ? ` --ns ${options.namespace}` : ''}`,
716
+ );
717
+ shellExec(
718
+ `${baseCommand} deploy${clusterFlag}${cmdString} --replicas ${replicas} --disable-update-proxy ${deployId} ${env} --versions ${versions}${
620
719
  options.namespace ? ` --namespace ${options.namespace}` : ''
621
- }${timeoutFlags}`,
720
+ }${timeoutFlags}${gitCleanFlag}`,
622
721
  );
623
722
  if (!targetTraffic)
624
723
  targetTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
@@ -830,6 +929,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
830
929
  const confInstances = JSON.parse(
831
930
  fs.readFileSync(`./engine-private/conf/${deployId}/conf.instances.json`, 'utf8'),
832
931
  );
932
+ let promotedTraffic = '';
833
933
  for (const instance of confInstances) {
834
934
  let {
835
935
  id: _id,
@@ -838,17 +938,23 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
838
938
  image: _image,
839
939
  fromPort: _fromPort,
840
940
  toPort: _toPort,
941
+ fromDebugPort: _fromDebugPort,
942
+ toDebugPort: _toDebugPort,
841
943
  cmd: _cmd,
842
944
  volumes: _volumes,
843
945
  metadata: _metadata,
844
946
  } = instance;
845
947
  if (id !== _id) continue;
846
948
  const _deployId = `${deployId}-${_id}`;
949
+ // Use debug ports in development when defined, fall back to production ports.
950
+ if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
951
+ if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
847
952
  const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
848
953
  hostTest: _host,
849
954
  namespace: options.namespace,
850
955
  });
851
956
  const targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
957
+ promotedTraffic = targetTraffic;
852
958
  let proxyYaml =
853
959
  Underpost.deploy.baseProxyYamlFactory({ host: _host, env: options.tls ? 'production' : env, options }) +
854
960
  Underpost.deploy.deploymentYamlServiceFactory({
@@ -874,6 +980,18 @@ EOF
874
980
  { disableLog: true },
875
981
  );
876
982
  }
983
+ // Refresh the gRPC service to ensure it points to the parent deploy's current traffic.
984
+ if (promotedTraffic) {
985
+ const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
986
+ const grpcServicePath = Underpost.deploy.buildGrpcServiceManifest({
987
+ deployId,
988
+ env,
989
+ confServer: loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
990
+ namespace: options.namespace,
991
+ traffic: [parentTraffic],
992
+ });
993
+ if (grpcServicePath) shellExec(`kubectl apply -f ${grpcServicePath} -n ${options.namespace}`);
994
+ }
877
995
  },
878
996
 
879
997
  /**
@@ -900,12 +1018,17 @@ EOF
900
1018
  image: _image,
901
1019
  fromPort: _fromPort,
902
1020
  toPort: _toPort,
1021
+ fromDebugPort: _fromDebugPort,
1022
+ toDebugPort: _toDebugPort,
903
1023
  cmd: _cmd,
904
1024
  volumes: _volumes,
905
1025
  metadata: _metadata,
906
1026
  } = instance;
907
1027
  if (id !== _id) continue;
908
1028
  const _deployId = `${deployId}-${_id}`;
1029
+ // Use debug ports in development when defined, fall back to production ports.
1030
+ if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
1031
+ if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
909
1032
  etcHosts.push(_host);
910
1033
  if (options.expose) continue;
911
1034
  // Examples images:
@@ -913,12 +1036,13 @@ EOF
913
1036
  // `localhost/rockylinux9-underpost:${Underpost.version}`
914
1037
  if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
915
1038
 
916
- if (options.nodeName) {
917
- shellExec(`sudo crictl pull ${_image}`);
918
- } else {
919
- shellExec(`docker pull ${_image}`);
920
- shellExec(`sudo kind load docker-image ${_image}`);
921
- }
1039
+ if (_image && !_image.startsWith('localhost'))
1040
+ Underpost.image.pullDockerHubImage({
1041
+ dockerhubImage: _image,
1042
+ kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
1043
+ kubeadm: options.nodeName || options.kubeadm,
1044
+ k3s: options.k3s,
1045
+ });
922
1046
 
923
1047
  const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
924
1048
  hostTest: _host,
@@ -927,7 +1051,7 @@ EOF
927
1051
 
928
1052
  const targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
929
1053
  const podId = `${_deployId}-${env}-${targetTraffic}`;
930
- const ignorePods = Underpost.deploy.get(podId, 'pods', options.namespace).map((p) => p.NAME);
1054
+ const ignorePods = Underpost.kubectl.get(podId, 'pods', options.namespace).map((p) => p.NAME);
931
1055
  Underpost.deploy.configMap(env, options.namespace);
932
1056
  shellExec(`kubectl delete service ${podId}-service --namespace ${options.namespace} --ignore-not-found`);
933
1057
  shellExec(`kubectl delete deployment ${podId} --namespace ${options.namespace} --ignore-not-found`);
@@ -939,7 +1063,30 @@ EOF
939
1063
  env,
940
1064
  version: targetTraffic,
941
1065
  nodeName: options.nodeName,
1066
+ clusterContext: options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind',
1067
+ gitClean: options.gitClean || false,
942
1068
  });
1069
+ // Regenerate the parent deploy's gRPC ClusterIP service pointing to the
1070
+ // parent's current traffic colour and apply it before the instance pod starts so
1071
+ // DNS is resolvable the moment the pod boots.
1072
+ const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
1073
+ const grpcServicePath = Underpost.deploy.buildGrpcServiceManifest({
1074
+ deployId,
1075
+ env,
1076
+ confServer: loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
1077
+ namespace: options.namespace,
1078
+ traffic: [targetTraffic],
1079
+ host: _host,
1080
+ });
1081
+ if (grpcServicePath) shellExec(`kubectl apply -f ${grpcServicePath} -n ${options.namespace}`);
1082
+
1083
+ const resolvedCmd = _cmd[env].map((c) =>
1084
+ c.replaceAll(
1085
+ '{{grpc-service-dns}}',
1086
+ `${deployId}-grpc-service-${env}-${parentTraffic}.${options.namespace || 'default'}.svc.cluster.local:50051`,
1087
+ ),
1088
+ );
1089
+
943
1090
  let deploymentYaml = `---
944
1091
  ${Underpost.deploy
945
1092
  .deploymentYamlPartsFactory({
@@ -951,7 +1098,7 @@ ${Underpost.deploy
951
1098
  image: _image,
952
1099
  namespace: options.namespace,
953
1100
  volumes: _volumes,
954
- cmd: _cmd[env],
1101
+ cmd: resolvedCmd,
955
1102
  })
956
1103
  .replace('{{ports}}', buildKindPorts(_fromPort, _toPort))}
957
1104
  `;
@@ -969,7 +1116,6 @@ EOF
969
1116
  targetTraffic,
970
1117
  ignorePods,
971
1118
  options.namespace,
972
- options.logType,
973
1119
  );
974
1120
 
975
1121
  if (!ready) {
@@ -989,6 +1135,153 @@ EOF
989
1135
  }
990
1136
  },
991
1137
 
1138
+ /**
1139
+ * @method instance-build-manifest
1140
+ * @description Builds a Kubernetes Deployment + Service manifest for a specific instance entry
1141
+ * from `conf.instances.json` and writes it to a file.
1142
+ * Traffic colour is automatically chosen as the opposite of the current live colour (blue/green),
1143
+ * defaulting to `blue` when no deployment is running yet.
1144
+ *
1145
+ * If `--build` is supplied the image is built from the project Dockerfile and loaded into the
1146
+ * cluster before the manifest is written (kind by default; `--kubeadm` / `--k3s` override).
1147
+ *
1148
+ * @param {string} path - Comma-separated: `deployId,instanceId[,projectPath]`.
1149
+ * `projectPath` is the root directory that contains the `Dockerfile` (e.g. `./cyberia-client`).
1150
+ * Artifacts are written to `<projectPath>/manifests/<env>/Dockerfile` and
1151
+ * `<projectPath>/manifests/<env>/deployment.yaml`.
1152
+ * In production, files are also copied to `<projectPath>/Dockerfile` and
1153
+ * `<projectPath>/deployment.yaml`.
1154
+ * @param {Object} options - The default underpost runner options for customizing workflow
1155
+ * @memberof UnderpostRun
1156
+ */
1157
+ 'instance-build-manifest': (path, options = DEFAULT_OPTION) => {
1158
+ const env = options.dev ? 'development' : 'production';
1159
+ let [deployId, id, projectPath] = path.split(',');
1160
+ const rootPath = projectPath ? projectPath : '.';
1161
+ const envManifestPath = `${rootPath}/manifests/deployments/${id}-${env}`;
1162
+ const outputPath = `${envManifestPath}/deployment.yaml`;
1163
+ const dockerfileManifestPath = `${envManifestPath}/Dockerfile`;
1164
+
1165
+ fs.mkdirpSync(envManifestPath);
1166
+
1167
+ const confInstances = JSON.parse(
1168
+ fs.readFileSync(`./engine-private/conf/${deployId}/conf.instances.json`, 'utf8'),
1169
+ );
1170
+
1171
+ const instance = confInstances.find((i) => i.id === id);
1172
+ if (!instance) {
1173
+ logger.error(`Instance with id '${id}' not found in conf.instances.json for deployId '${deployId}'`);
1174
+ return;
1175
+ }
1176
+
1177
+ let {
1178
+ id: _id,
1179
+ host: _host,
1180
+ path: _path,
1181
+ image: _image,
1182
+ fromPort: _fromPort,
1183
+ toPort: _toPort,
1184
+ fromDebugPort: _fromDebugPort,
1185
+ toDebugPort: _toDebugPort,
1186
+ cmd: _cmd,
1187
+ volumes: _volumes,
1188
+ metadata: _metadata,
1189
+ runtime: _runtime,
1190
+ } = instance;
1191
+
1192
+ // Resolve Dockerfile source: use runtime-specific path when instance defines a runtime.
1193
+ const dockerfileSourcePath = _runtime ? `src/runtime/${_runtime}/Dockerfile` : `${rootPath}/Dockerfile`;
1194
+ if (fs.existsSync(dockerfileSourcePath)) {
1195
+ fs.copyFileSync(dockerfileSourcePath, dockerfileManifestPath);
1196
+ } else {
1197
+ logger.warn(`[instance-build-manifest] Dockerfile not found at ${dockerfileSourcePath}`);
1198
+ }
1199
+
1200
+ const _deployId = `${deployId}-${_id}`;
1201
+ if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
1202
+ // Use debug ports in development when defined, fall back to production ports.
1203
+ if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
1204
+ if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
1205
+
1206
+ // Build image from projectPath Dockerfile and load into cluster when --build is set.
1207
+ if (options.build && projectPath) {
1208
+ const isKind = !options.kubeadm && !options.k3s;
1209
+ Underpost.image.build({
1210
+ path: projectPath,
1211
+ imageName: _image,
1212
+ podmanSave: true,
1213
+ imagePath: projectPath,
1214
+ kind: isKind,
1215
+ kubeadm: !!options.kubeadm,
1216
+ k3s: !!options.k3s,
1217
+ reset: !!options.reset,
1218
+ dev: options.dev,
1219
+ });
1220
+ logger.info(`[instance-build-manifest] Image built and loaded`, {
1221
+ image: _image,
1222
+ cluster: isKind ? 'kind' : options.kubeadm ? 'kubeadm' : 'k3s',
1223
+ });
1224
+ }
1225
+
1226
+ // Determine target traffic: opposite of current, or 'blue' if nothing is running yet.
1227
+ const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
1228
+ hostTest: _host,
1229
+ namespace: options.namespace,
1230
+ });
1231
+ const targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
1232
+
1233
+ // Resolve {{grpc-service-dns}} using the parent deploy's current (or default) traffic.
1234
+ const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
1235
+ const resolvedCmd = _cmd[env].map((c) =>
1236
+ c.replaceAll(
1237
+ '{{grpc-service-dns}}',
1238
+ `${deployId}-grpc-service-${env}-${parentTraffic}.${options.namespace || 'default'}.svc.cluster.local:50051`,
1239
+ ),
1240
+ );
1241
+
1242
+ const deploymentYaml =
1243
+ `---\n` +
1244
+ Underpost.deploy
1245
+ .deploymentYamlPartsFactory({
1246
+ deployId: _deployId,
1247
+ env,
1248
+ suffix: targetTraffic,
1249
+ resources: Underpost.deploy.resourcesFactory(options),
1250
+ replicas: options.replicas,
1251
+ image: _image,
1252
+ namespace: options.namespace,
1253
+ volumes: _volumes,
1254
+ cmd: resolvedCmd,
1255
+ })
1256
+ .replace('{{ports}}', buildKindPorts(_fromPort, _toPort));
1257
+
1258
+ fs.writeFileSync(outputPath, deploymentYaml, 'utf8');
1259
+ logger.info(`[instance-build-manifest] Manifest written to ${outputPath}`, {
1260
+ deployId: _deployId,
1261
+ env,
1262
+ traffic: targetTraffic,
1263
+ image: _image,
1264
+ });
1265
+
1266
+ if (env === 'production') {
1267
+ if (fs.existsSync(dockerfileManifestPath)) {
1268
+ fs.copyFileSync(dockerfileManifestPath, `${rootPath}/Dockerfile`);
1269
+ }
1270
+ fs.copyFileSync(outputPath, `${rootPath}/deployment.yaml`);
1271
+ logger.info('[instance-build-manifest] Production artifacts copied to project root', {
1272
+ rootPath,
1273
+ dockerfile: `${rootPath}/Dockerfile`,
1274
+ deployment: `${rootPath}/deployment.yaml`,
1275
+ });
1276
+ const ciSrc = `./.github/workflows/docker-image.${_runtime}.ci.yml`;
1277
+ if (fs.existsSync(ciSrc)) {
1278
+ if (!fs.existsSync(`${rootPath}/.github/workflows`)) fs.mkdirpSync(`${rootPath}/.github/workflows`);
1279
+ fs.copyFileSync(ciSrc, `${rootPath}/.github/workflows/docker-image.${_runtime}.ci.yml`);
1280
+ logger.info(`[instance-build-manifest] CI workflow copied`, { src: ciSrc });
1281
+ }
1282
+ }
1283
+ },
1284
+
992
1285
  /**
993
1286
  * @method ls-deployments
994
1287
  * @description Retrieves and logs a table of Kubernetes deployments using `Underpost.deploy.get`.
@@ -997,7 +1290,7 @@ EOF
997
1290
  * @memberof UnderpostRun
998
1291
  */
999
1292
  'ls-deployments': async (path, options = DEFAULT_OPTION) => {
1000
- console.table(await Underpost.deploy.get(path, 'deployments', options.namespace));
1293
+ console.table(await Underpost.kubectl.get(path, 'deployments', options.namespace));
1001
1294
  },
1002
1295
 
1003
1296
  /**
@@ -1013,6 +1306,44 @@ EOF
1013
1306
  shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh${options.dev ? ' --install-dev' : ``}`);
1014
1307
  },
1015
1308
 
1309
+ /**
1310
+ * @method install-crio
1311
+ * @description Installs and configures CRI-O as the container runtime for kubeadm clusters.
1312
+ * Adds the stable v1.33 CRI-O yum repository, installs the `cri-o` package, configures
1313
+ * the systemd cgroup driver, enables the `crio` service, and writes `/etc/crictl.yaml`
1314
+ * so that `crictl` targets the CRI-O socket by default.
1315
+ * @param {string} path - Unused.
1316
+ * @param {Object} options - The default underpost runner options for customizing workflow.
1317
+ * @memberof UnderpostRun
1318
+ */
1319
+ 'install-crio': (path, options = DEFAULT_OPTION) => {
1320
+ logger.info('Installing CRI-O...');
1321
+ shellExec(`cat <<EOF | sudo tee /etc/yum.repos.d/cri-o.repo
1322
+ [cri-o]
1323
+ name=CRI-O
1324
+ baseurl=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/v1.33/rpm/
1325
+ enabled=1
1326
+ gpgcheck=1
1327
+ gpgkey=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/v1.33/rpm/repodata/repomd.xml.key
1328
+ EOF`);
1329
+ shellExec(`sudo dnf -y install cri-o`);
1330
+ // crictl is in the kubernetes repo but excluded by default — install it explicitly
1331
+ shellExec(`sudo yum install -y cri-tools --disableexcludes=kubernetes`);
1332
+ // Ensure CRI-O uses systemd cgroup driver (matches kubelet default)
1333
+ shellExec(
1334
+ `sudo sed -i 's/^#\?cgroup_manager =.*/cgroup_manager = "systemd"/' /etc/crio/crio.conf 2>/dev/null || true`,
1335
+ );
1336
+ shellExec(`sudo systemctl enable --now crio`);
1337
+ logger.info('CRI-O installed and started.');
1338
+ // Write crictl config so all crictl calls default to the CRI-O socket
1339
+ shellExec(`cat <<EOF | sudo tee /etc/crictl.yaml
1340
+ runtime-endpoint: unix:///var/run/crio/crio.sock
1341
+ image-endpoint: unix:///var/run/crio/crio.sock
1342
+ timeout: 10
1343
+ debug: false
1344
+ EOF`);
1345
+ },
1346
+
1016
1347
  /**
1017
1348
  * @method dd-container
1018
1349
  * @description Deploys a development or debug container tasks jobs, setting up necessary volumes and images, and running specified commands within the container.
@@ -1091,15 +1422,13 @@ EOF
1091
1422
  'db-client': async (path, options = DEFAULT_OPTION) => {
1092
1423
  const { underpostRoot } = options;
1093
1424
 
1094
- const image = 'adminer:4.7.6-standalone';
1095
-
1096
- if (!options.kubeadm && !options.k3s) {
1097
- // Only load if not kubeadm/k3s (Kind needs it)
1098
- shellExec(`docker pull ${image}`);
1099
- shellExec(`sudo kind load docker-image ${image}`);
1100
- } else if (options.kubeadm || options.k3s)
1101
- // For kubeadm/k3s, ensure it's available for containerd
1102
- shellExec(`sudo crictl pull ${image}`);
1425
+ Underpost.image.pullDockerHubImage({
1426
+ dockerhubImage: 'adminer',
1427
+ version: '4.7.6-standalone',
1428
+ kind: options.kind,
1429
+ kubeadm: options.kubeadm,
1430
+ k3s: options.k3s,
1431
+ });
1103
1432
 
1104
1433
  shellExec(`kubectl delete deployment adminer -n ${options.namespace} --ignore-not-found`);
1105
1434
  shellExec(`kubectl apply -k ${underpostRoot}/manifests/deployment/adminer/. -n ${options.namespace}`);
@@ -1157,6 +1486,9 @@ EOF
1157
1486
  /**
1158
1487
  * @method promote
1159
1488
  * @description Switches traffic between blue/green deployments for a specified deployment ID(s) (uses `dd.router` for 'dd', or a specific ID).
1489
+ * When `--tls` is set, rebuilds the proxy manifest with `--cert` so the HTTPProxy includes
1490
+ * TLS config, deletes stale Certificate resources, then reapplies the proxy and secret.yaml
1491
+ * (cert-manager Certificate resources) for each affected deployment.
1160
1492
  * @param {string} path - The input value, identifier, or path for the operation (used as a comma-separated string: `deployId,env,replicas`).
1161
1493
  * @param {Object} options - The default underpost runner options for customizing workflow
1162
1494
  * @memberof UnderpostRun
@@ -1165,11 +1497,34 @@ EOF
1165
1497
  let [inputDeployId, inputEnv, inputReplicas] = path.split(',');
1166
1498
  if (!inputEnv) inputEnv = 'production';
1167
1499
  if (!inputReplicas) inputReplicas = 1;
1500
+ // TODO: normalize: --tls maps to --cert for deploy.js isValidTLSContext compatibility
1501
+ if (options.tls) options.cert = true;
1502
+
1503
+ const applyCerts = (deployId, targetTraffic) => {
1504
+ if (!options.tls) return;
1505
+ // Rebuild proxy.yaml with --cert so the HTTPProxy includes TLS virtualhost config
1506
+ shellExec(
1507
+ `node bin deploy --build-manifest --cert --traffic ${targetTraffic} --replicas ${inputReplicas} --namespace ${options.namespace} ${deployId} ${inputEnv}`,
1508
+ );
1509
+ // Delete stale Certificate resources before reapplying
1510
+ const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
1511
+ if (fs.existsSync(confServerPath)) {
1512
+ for (const host of Object.keys(JSON.parse(fs.readFileSync(confServerPath, 'utf8'))))
1513
+ shellExec(`sudo kubectl delete Certificate ${host} -n ${options.namespace} --ignore-not-found`);
1514
+ }
1515
+ shellExec(
1516
+ `sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${inputEnv}/proxy.yaml -n ${options.namespace}`,
1517
+ );
1518
+ const secretPath = `./engine-private/conf/${deployId}/build/${inputEnv}/secret.yaml`;
1519
+ if (fs.existsSync(secretPath)) shellExec(`kubectl apply -f ${secretPath} -n ${options.namespace}`);
1520
+ };
1521
+
1168
1522
  if (inputDeployId === 'dd') {
1169
1523
  for (const deployId of fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',')) {
1170
1524
  const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
1171
1525
  const targetTraffic = currentTraffic === 'blue' ? 'green' : 'blue';
1172
1526
  Underpost.deploy.switchTraffic(deployId, inputEnv, targetTraffic, inputReplicas, options.namespace, options);
1527
+ applyCerts(deployId, targetTraffic);
1173
1528
  }
1174
1529
  } else {
1175
1530
  const currentTraffic = Underpost.deploy.getCurrentTraffic(inputDeployId, { namespace: options.namespace });
@@ -1182,6 +1537,7 @@ EOF
1182
1537
  options.namespace,
1183
1538
  options,
1184
1539
  );
1540
+ applyCerts(inputDeployId, targetTraffic);
1185
1541
  }
1186
1542
  },
1187
1543
  /**
@@ -1662,7 +2018,7 @@ EOF
1662
2018
  : [
1663
2019
  `npm install -g npm@11.2.0`,
1664
2020
  `npm install -g underpost`,
1665
- `${baseCommand} secret underpost --create-from-file /etc/config/.env.${env}`,
2021
+ `${baseCommand} secret underpost --create-from-env`,
1666
2022
  `${baseCommand} start --build --run ${deployId} ${env}`,
1667
2023
  ];
1668
2024
  shellExec(`node bin run sync${baseClusterCommand} --deploy-id-cron-jobs none dd-test --cmd "${cmd}"`);
@@ -1693,7 +2049,7 @@ EOF
1693
2049
 
1694
2050
  const { close } = await (async () => {
1695
2051
  const checkAwaitPath = '/await';
1696
- while (!Underpost.deploy.existsContainerFile({ podName, path: checkAwaitPath })) {
2052
+ while (!Underpost.kubectl.existsFile({ podName, path: checkAwaitPath })) {
1697
2053
  logger.info('monitor', checkAwaitPath);
1698
2054
  await timer(1000);
1699
2055
  }
@@ -1720,7 +2076,7 @@ EOF
1720
2076
  logger.info('monitor', checkPath);
1721
2077
  {
1722
2078
  const checkAwaitPath = `/home/dd/docs${checkPath}`;
1723
- while (!Underpost.deploy.existsContainerFile({ podName, path: checkAwaitPath })) {
2079
+ while (!Underpost.kubectl.existsFile({ podName, path: checkAwaitPath })) {
1724
2080
  logger.info('waiting for', checkAwaitPath);
1725
2081
  await timer(1000);
1726
2082
  }
@@ -1786,7 +2142,8 @@ EOF
1786
2142
 
1787
2143
  shellCd(dir);
1788
2144
 
1789
- shellExec(`git init && git add . && git commit -m "Base implementation"`);
2145
+ Underpost.repo.initLocalRepo({ path: dir });
2146
+ shellExec(`git add . && git commit -m "Base implementation"`);
1790
2147
  shellExec(`chmod +x ./replace_params.sh`);
1791
2148
  shellExec(`chmod +x ./build.sh`);
1792
2149
 
@@ -1795,6 +2152,16 @@ EOF
1795
2152
 
1796
2153
  shellCd('/home/dd/engine');
1797
2154
  },
2155
+ /**
2156
+ * @method pull-rocky-image
2157
+ * @description Pulls the base `rockylinux:9` image from Docker Hub via Podman.
2158
+ * @param {string} path - The input value, identifier, or path for the operation.
2159
+ * @param {Object} options - The default underpost runner options for customizing workflow
2160
+ * @memberof UnderpostRun
2161
+ */
2162
+ 'pull-rocky-image': (path, options = DEFAULT_OPTION) => {
2163
+ shellExec(`sudo podman pull docker.io/library/rockylinux:9`);
2164
+ },
1798
2165
  /**
1799
2166
  * @method rmi
1800
2167
  * @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
@@ -1824,6 +2191,42 @@ EOF
1824
2191
  } else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
1825
2192
  }
1826
2193
  },
2194
+ /**
2195
+ * @method generate-pass
2196
+ * @description Generates a cryptographically secure random password that satisfies all validatePassword
2197
+ * constraints (lowercase, uppercase, digit, special char, min 8 chars). Logs the plain password
2198
+ * to the console or, when `--copy` is set, copies it to the clipboard via pbcopy.
2199
+ * @param {string} path - Optional password length (default: 16).
2200
+ * @param {Object} options - The default underpost runner options for customizing workflow.
2201
+ * @param {boolean} options.copy - When true, copies to clipboard instead of logging.
2202
+ * @memberof UnderpostRun
2203
+ */
2204
+ 'generate-pass': (path, options = DEFAULT_OPTION) => {
2205
+ const length = path && parseInt(path) > 0 ? parseInt(path) : 16;
2206
+ const lower = 'abcdefghijklmnopqrstuvwxyz';
2207
+ const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
2208
+ const digits = '0123456789';
2209
+ const special = '@#$%^&*()_+';
2210
+ const all = lower + upper + digits + special;
2211
+ const buf = crypto.randomBytes(length + 4);
2212
+ // Guarantee at least one character from each required class
2213
+ const chars = [
2214
+ lower[buf[0] % lower.length],
2215
+ upper[buf[1] % upper.length],
2216
+ digits[buf[2] % digits.length],
2217
+ special[buf[3] % special.length],
2218
+ ];
2219
+ for (let i = 4; i < length; i++) chars.push(all[buf[i] % all.length]);
2220
+ // Fisher-Yates shuffle using an independent random buffer
2221
+ const shuf = crypto.randomBytes(length);
2222
+ for (let i = chars.length - 1; i > 0; i--) {
2223
+ const j = shuf[i % shuf.length] % (i + 1);
2224
+ [chars[i], chars[j]] = [chars[j], chars[i]];
2225
+ }
2226
+ const password = chars.join('');
2227
+ if (options.copy) pbcopy(password);
2228
+ else console.log(password);
2229
+ },
1827
2230
  /**
1828
2231
  * @method secret
1829
2232
  * @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
@@ -1833,9 +2236,7 @@ EOF
1833
2236
  */
1834
2237
  secret: (path, options = DEFAULT_OPTION) => {
1835
2238
  const secretPath = path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`;
1836
- const command = options.dev
1837
- ? `node bin secret underpost --create-from-file ${secretPath}`
1838
- : `underpost secret underpost --create-from-file ${secretPath}`;
2239
+ const command = `${options.dev ? 'node bin' : 'underpost'} secret underpost --create-from-file ${secretPath}`;
1839
2240
  shellExec(command);
1840
2241
  },
1841
2242
  /**
@@ -2006,6 +2407,144 @@ EOF`;
2006
2407
  if (options.logs) shellExec(`kubectl logs -f ${podName} -n ${namespace}`, { async: true });
2007
2408
  }
2008
2409
  },
2410
+
2411
+ /**
2412
+ * @method push-bundle
2413
+ * @description Builds the client zip for the specified deployment, splits it into parts, and uploads to file storage.
2414
+ * Steps: set env, build+split zip, switch to cron env, upload parts to storage.
2415
+ * @param {string} path - Optional `fsPath.splitOption` string.
2416
+ * Examples: `build` (default split 8), `build.16` (split 16 MB), `build.none-split` (no split flag).
2417
+ * @param {Object} options - The default underpost runner options for customizing workflow.
2418
+ * @param {string} [options.deployId] - Override deploy ID.
2419
+ * @param {boolean} [options.dev] - Use development environment; defaults to production.
2420
+ * @memberof UnderpostRun
2421
+ */
2422
+ 'push-bundle': (path = '', options = DEFAULT_OPTION) => {
2423
+ const baseCommand = options.dev ? 'node bin' : 'underpost';
2424
+ const env = options.dev ? 'development' : 'production';
2425
+ const deployId = options.deployId || 'dd-default';
2426
+ const pathParts = (path || '').split('.');
2427
+ const fsPath = (pathParts[0] || '').trim() || 'build';
2428
+ const splitOption = (pathParts[1] || '').trim();
2429
+
2430
+ let splitFlag = '--split 8';
2431
+ if (splitOption) {
2432
+ if (splitOption === 'none-split') {
2433
+ splitFlag = '';
2434
+ } else {
2435
+ const splitMb = Number(splitOption);
2436
+ if (Number.isFinite(splitMb) && splitMb > 0) {
2437
+ splitFlag = `--split ${splitMb}`;
2438
+ } else {
2439
+ logger.warn('push-bundle: invalid split option, using default split 8', {
2440
+ path,
2441
+ splitOption,
2442
+ });
2443
+ }
2444
+ }
2445
+ }
2446
+
2447
+ shellExec(`${baseCommand} env ${deployId} ${env}`);
2448
+ shellExec(`${baseCommand} client ${deployId} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
2449
+ shellExec(
2450
+ `${baseCommand} fs ${fsPath} --recursive --deploy-id ${deployId} --storage-file-path engine-private/conf/${deployId}/storage.bundle.json --force`,
2451
+ );
2452
+ },
2453
+
2454
+ /**
2455
+ * @method pull-bundle
2456
+ * @description Downloads split zip parts from file storage, merges and extracts them, and moves the result into the public directory.
2457
+ * Steps: set env, download parts (omit-unzip), merge zip, unzip, remove zip + parts, move to public/<host>[/path].
2458
+ * Iterates over every non-singleReplica, non-redirect, non-disabledRebuild route in conf.server.json
2459
+ * so that multi-path deployments are handled correctly.
2460
+ * @param {string} path - Optional comma-separated host name(s) to restrict processing (e.g. 'underpost.net' or 'a.com,b.com').
2461
+ * If omitted, all hosts from `engine-private/conf/<deployId>/conf.server.json` are used.
2462
+ * @param {Object} options - The default underpost runner options for customizing workflow.
2463
+ * @param {string} [options.deployId] - Deploy ID for storage lookup (defaults to 'dd-default').
2464
+ * @param {boolean} [options.dev] - Use development environment; defaults to production.
2465
+ * @memberof UnderpostRun
2466
+ */
2467
+ 'pull-bundle': (path = '', options = DEFAULT_OPTION) => {
2468
+ const baseCommand = options.dev ? 'node bin' : 'underpost';
2469
+ const env = options.dev ? 'development' : 'production';
2470
+ const deployId = options.deployId || 'dd-default';
2471
+ const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
2472
+ const confServer = fs.existsSync(confServerPath) ? loadConfServerJson(confServerPath) : {};
2473
+ const hostsArg = path
2474
+ ? path
2475
+ .split(',')
2476
+ .map((h) => h.trim())
2477
+ .filter(Boolean)
2478
+ : Object.keys(confServer);
2479
+
2480
+ if (hostsArg.length === 0) {
2481
+ logger.error('pull-bundle: no hosts resolved', { deployId, path, confServerPath });
2482
+ return;
2483
+ }
2484
+
2485
+ shellExec(`${baseCommand} env ${deployId} ${env}`);
2486
+ if (!fs.existsSync('./build')) fs.mkdirSync('./build', { recursive: true });
2487
+ shellExec(
2488
+ `${baseCommand} fs build --recursive --deploy-id ${deployId} --storage-file-path engine-private/conf/${deployId}/storage.bundle.json --pull --omit-unzip`,
2489
+ );
2490
+
2491
+ for (const host of hostsArg) {
2492
+ // Gather all routes for this host; fall back to root '/' when host is not in confServer
2493
+ // (e.g. when hosts were provided explicitly via the path argument).
2494
+ const routePaths = confServer[host] ? Object.keys(confServer[host]) : ['/'];
2495
+
2496
+ for (const routePath of routePaths) {
2497
+ const routeConf = confServer[host] ? confServer[host][routePath] || {} : {};
2498
+ // Skip routes that are not built by buildClient (mirrors buildClient skip conditions)
2499
+ if (routeConf.singleReplica || routeConf.redirect || routeConf.disabledRebuild) continue;
2500
+
2501
+ // buildClient names the zip as "<host>-<path-no-slashes>.zip"
2502
+ // e.g. host="underpost.net", path="/" → buildId="underpost.net-", zip="build/underpost.net-.zip"
2503
+ // e.g. host="app.net", path="/admin" → buildId="app.net-admin", zip="build/app.net-admin.zip"
2504
+ const buildId = `${host}-${routePath.replaceAll('/', '')}`;
2505
+ const zipPath = `build/${buildId}.zip`;
2506
+ const buildDir = './build';
2507
+ const hasZip = fs.existsSync(zipPath);
2508
+ const hasParts =
2509
+ fs.existsSync(buildDir) &&
2510
+ fs
2511
+ .readdirSync(buildDir)
2512
+ .some((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`));
2513
+
2514
+ if (!hasZip && !hasParts) {
2515
+ logger.warn(`Bundle not found for '${host}${routePath}'. Skipping.`, { zipPath, deployId });
2516
+ continue;
2517
+ }
2518
+
2519
+ if (hasParts) shellExec(`${baseCommand} client --merge-zip ${zipPath}`);
2520
+ shellExec(`${baseCommand} client --unzip ${zipPath}`);
2521
+ shellExec(`sudo rm -rf ${zipPath}`);
2522
+
2523
+ // Clean up downloaded part wrapper zips left by --omit-unzip pull
2524
+ if (fs.existsSync(buildDir)) {
2525
+ fs.readdirSync(buildDir)
2526
+ .filter((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`))
2527
+ .forEach((partFile) => shellExec(`sudo rm -rf ${buildDir}/${partFile}`));
2528
+ }
2529
+
2530
+ // unzipClientBuild extracts to buildId with trailing '-' stripped
2531
+ // e.g. "build/underpost.net-" → "build/underpost.net"
2532
+ // e.g. "build/app.net-admin" → "build/app.net-admin" (no trailing dash, no change)
2533
+ const extractedDir = `build/${buildId.replace(/-$/, '')}`;
2534
+ if (!fs.existsSync(extractedDir)) {
2535
+ logger.warn(`Extracted build dir not found: ${extractedDir}. Skipping move for '${host}${routePath}'.`);
2536
+ continue;
2537
+ }
2538
+
2539
+ // Destination mirrors the public directory layout used by the server
2540
+ const publicDestPath = routePath === '/' ? `public/${host}` : `public/${host}${routePath}`;
2541
+ if (fs.existsSync(publicDestPath)) shellExec(`sudo rm -rf ${publicDestPath}`);
2542
+ // Ensure parent directory exists for sub-paths
2543
+ if (routePath !== '/') shellExec(`sudo mkdir -p public/${host}`);
2544
+ shellExec(`sudo mv ${extractedDir} ${publicDestPath}`);
2545
+ }
2546
+ }
2547
+ },
2009
2548
  };
2010
2549
 
2011
2550
  static API = {