@underpostnet/cyberia 3.3.0 → 3.3.76

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 (568) hide show
  1. package/.dockerignore +1 -0
  2. package/.env.example +17 -5
  3. package/.github/workflows/coverall.cyberia.ci.yml +39 -14
  4. package/.github/workflows/cyberia-client.cd.yml +5 -0
  5. package/.github/workflows/cyberia-server.cd.yml +5 -0
  6. package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
  7. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
  8. package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
  9. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
  10. package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
  11. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
  12. package/.github/workflows/engine-cyberia.cd.yml +3 -0
  13. package/.github/workflows/engine-cyberia.ci.yml +4 -5
  14. package/.github/workflows/ghpkg.ci.yml +35 -20
  15. package/.github/workflows/hardhat.ci.yml +12 -11
  16. package/.github/workflows/npmpkg.ci.yml +17 -15
  17. package/.github/workflows/publish.cyberia.ci.yml +44 -0
  18. package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
  19. package/.github/workflows/release.cd.yml +1 -0
  20. package/.prettierignore +1 -0
  21. package/AGENTS.md +96 -0
  22. package/CHANGELOG.md +653 -2513
  23. package/CLI-HELP.md +323 -157
  24. package/Dockerfile +25 -9
  25. package/Dockerfile.dev +25 -9
  26. package/Dockerfile.test +5 -5
  27. package/README.md +48 -28
  28. package/bin/build.js +86 -41
  29. package/bin/build.template.js +8 -3
  30. package/bin/cyberia.js +1494 -1403
  31. package/bin/deploy.js +18 -104
  32. package/bin/index.js +1494 -1403
  33. package/compose.env +17 -5
  34. package/conf.js +98 -7
  35. package/deploy/cyberia-client/deploy.sh +46 -0
  36. package/deploy/cyberia-client/package.sh +17 -0
  37. package/deploy/cyberia-client/state.sh +15 -0
  38. package/deploy/cyberia-server/deploy.sh +46 -0
  39. package/deploy/cyberia-server/package.sh +17 -0
  40. package/deploy/cyberia-server/state.sh +15 -0
  41. package/deploy/dd-cyberia/deploy.sh +9 -29
  42. package/deploy/dd-cyberia/init.sh +38 -45
  43. package/deploy/dd-cyberia/package.sh +16 -0
  44. package/deploy/dd-cyberia/state.sh +14 -0
  45. package/deploy/dd-cyberia/sync-deploy.sh +133 -147
  46. package/deploy/lib/config.sh +29 -0
  47. package/deploy/lib/github-actions-logging.sh +610 -0
  48. package/deploy/lib/host.sh +194 -0
  49. package/deploy/lib/state.sh +65 -0
  50. package/deploy/pwa-microservices-template/deploy.sh +17 -42
  51. package/deploy/release/deploy.sh +9 -41
  52. package/deployment.yaml +7 -2
  53. package/docker-compose.yml +23 -17
  54. package/examples/static-page/README.md +19 -19
  55. package/examples/static-page/ssr-components/CustomPage.js +2 -2
  56. package/hardhat/hardhat.config.js +1 -6
  57. package/hardhat/package-lock.json +101 -40
  58. package/hardhat/package.json +2 -2
  59. package/manifests/alertmanager/deployment.yaml +81 -0
  60. package/manifests/blackbox-exporter/deployment.yaml +75 -0
  61. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
  62. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
  63. package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
  64. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  65. package/manifests/deployment/mongo-express/deployment.yaml +46 -27
  66. package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
  67. package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
  68. package/manifests/deployment/mongo-express/service.yaml +16 -0
  69. package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
  70. package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
  71. package/manifests/grafana/deployment.yaml +26 -7
  72. package/manifests/grafana/kustomization.yaml +1 -0
  73. package/manifests/grafana/service.yaml +6 -1
  74. package/manifests/mariadb/pv.yaml +1 -0
  75. package/manifests/mongodb/statefulset.yaml +9 -2
  76. package/manifests/mysql/pv-pvc.yaml +14 -1
  77. package/manifests/postgresql/pv.yaml +12 -1
  78. package/manifests/postgresql/pvc.yaml +1 -0
  79. package/manifests/prometheus/deployment.yaml +18 -22
  80. package/package.json +87 -12
  81. package/scripts/event-monitor.sh +56 -0
  82. package/scripts/k3s-node-setup.sh +27 -14
  83. package/scripts/kubeadm-node-setup.sh +31 -11
  84. package/scripts/maas-setup.sh +8 -8
  85. package/scripts/selinux-normalize.sh +907 -0
  86. package/scripts/shutdown-machine.sh +843 -0
  87. package/scripts/test-monitor.sh +5 -5
  88. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
  89. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
  90. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
  91. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
  92. package/src/api/core/core.controller.js +1 -1
  93. package/src/api/core/core.router.js +2 -2
  94. package/src/api/core/core.service.js +2 -2
  95. package/src/api/crypto/crypto.controller.js +1 -1
  96. package/src/api/crypto/crypto.service.js +1 -1
  97. package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
  98. package/src/api/cyberia-action/cyberia-action.router.js +3 -3
  99. package/src/api/cyberia-action/cyberia-action.service.js +5 -6
  100. package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
  101. package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
  102. package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
  103. package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
  104. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +2 -4
  105. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
  106. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
  107. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
  108. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
  109. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
  110. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
  111. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
  112. package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
  113. package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
  114. package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
  115. package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
  116. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
  117. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
  118. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -2
  119. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
  120. package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
  121. package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -40
  122. package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
  123. package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
  124. package/src/api/cyberia-instance/cyberia-instance-map.service.js +35 -142
  125. package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
  126. package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
  127. package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
  128. package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
  129. package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
  130. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
  131. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
  132. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
  133. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
  134. package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
  135. package/src/api/cyberia-map/cyberia-map.router.js +1 -1
  136. package/src/api/cyberia-map/cyberia-map.service.js +8 -4
  137. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
  138. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
  139. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
  140. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
  141. package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
  142. package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
  143. package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
  144. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
  145. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
  146. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
  147. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
  148. package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
  149. package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
  150. package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
  151. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -214
  152. package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
  153. package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
  154. package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
  155. package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
  156. package/src/api/default/default.controller.js +1 -1
  157. package/src/api/default/default.router.js +1 -1
  158. package/src/api/default/default.service.js +2 -2
  159. package/src/api/document/document.controller.js +1 -1
  160. package/src/api/document/document.router.js +1 -1
  161. package/src/api/document/document.service.js +2 -2
  162. package/src/api/file/file.controller.js +1 -1
  163. package/src/api/file/file.ref.js +153 -0
  164. package/src/api/file/file.ref.json +11 -2
  165. package/src/api/file/file.router.js +1 -1
  166. package/src/api/file/file.service.js +2 -2
  167. package/src/api/instance/instance.controller.js +1 -1
  168. package/src/api/instance/instance.router.js +1 -1
  169. package/src/api/instance/instance.service.js +2 -2
  170. package/src/api/ipfs/ipfs.controller.js +1 -1
  171. package/src/api/ipfs/ipfs.router.js +2 -2
  172. package/src/api/ipfs/ipfs.service.js +2 -2
  173. package/src/api/object-layer/object-layer.controller.js +1 -1
  174. package/src/api/object-layer/object-layer.model.js +47 -12
  175. package/src/api/object-layer/object-layer.router.js +12 -5
  176. package/src/api/object-layer/object-layer.service.js +116 -123
  177. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
  178. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
  179. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
  180. package/src/api/test/test.controller.js +1 -1
  181. package/src/api/test/test.service.js +1 -1
  182. package/src/api/user/guest.service.js +3 -3
  183. package/src/api/user/user.controller.js +1 -1
  184. package/src/api/user/user.router.js +2 -2
  185. package/src/api/user/user.service.js +4 -4
  186. package/src/api.js +5 -9
  187. package/src/cli/app.js +402 -0
  188. package/src/cli/baremetal.js +30 -35
  189. package/src/cli/client.js +306 -0
  190. package/src/cli/cloud-init.js +3 -3
  191. package/src/cli/cluster.js +191 -82
  192. package/src/cli/db.js +120 -68
  193. package/src/cli/deploy.js +157 -75
  194. package/src/cli/docker-compose.js +5 -5
  195. package/src/cli/domains.js +184 -0
  196. package/src/cli/dotenv-store.js +143 -0
  197. package/src/cli/event.js +2281 -0
  198. package/src/cli/fs-selection.js +115 -0
  199. package/src/cli/fs.js +159 -343
  200. package/src/cli/host.js +672 -0
  201. package/src/cli/image.js +4 -6
  202. package/src/cli/index.js +343 -151
  203. package/src/cli/ipfs.js +34 -30
  204. package/src/cli/kickstart.js +1 -1
  205. package/src/cli/kubectl.js +218 -41
  206. package/src/cli/lxd.js +3 -3
  207. package/src/cli/monitor.js +1359 -26
  208. package/src/cli/package.js +90 -0
  209. package/src/cli/release.js +16 -12
  210. package/src/cli/repository.js +586 -363
  211. package/src/cli/run.js +398 -843
  212. package/src/cli/secrets.js +2158 -964
  213. package/src/cli/ssh.js +478 -279
  214. package/src/cli/state.js +394 -0
  215. package/src/cli/static.js +2 -2
  216. package/src/cli/system.js +1 -1
  217. package/src/cli/test.js +203 -125
  218. package/src/cli/vultr.js +61 -21
  219. package/src/cli/wireguard.js +1112 -306
  220. package/src/client/components/core/CalendarCore.js +53 -96
  221. package/src/client/components/core/Docs.js +327 -303
  222. package/src/client/components/core/FileExplorer.js +0 -21
  223. package/src/client/components/core/Modal.js +56 -1
  224. package/src/client/components/core/PanelForm.js +4 -4
  225. package/src/client/components/core/Repository.js +56 -0
  226. package/src/client/components/core/Translate.js +7 -0
  227. package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
  228. package/src/client/components/core/Wallet.js +0 -11
  229. package/src/client/components/core/Worker.js +7 -1
  230. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
  231. package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
  232. package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
  233. package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
  234. package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
  235. package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
  236. package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
  237. package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
  238. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
  239. package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
  240. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -35
  241. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
  242. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
  243. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
  244. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
  245. package/src/client/components/default/AppShellDefault.js +1 -1
  246. package/src/client/components/default/SettingsDefault.js +2 -2
  247. package/src/client/components/itemledger/SettingsItemledger.js +2 -2
  248. package/src/client/components/underpost/AppShellUnderpost.js +3 -2
  249. package/src/client/components/underpost/SettingsUnderpost.js +2 -2
  250. package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
  251. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
  252. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
  253. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
  254. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
  255. package/src/client/public/cyberia-docs/CYBERIA.md +48 -28
  256. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
  257. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
  258. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  259. package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
  260. package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
  261. package/src/client/public/default/android-chrome-144x144.png +0 -0
  262. package/src/client/public/default/android-chrome-192x192.png +0 -0
  263. package/src/client/public/default/android-chrome-256x256.png +0 -0
  264. package/src/client/public/default/android-chrome-36x36.png +0 -0
  265. package/src/client/public/default/android-chrome-384x384.png +0 -0
  266. package/src/client/public/default/android-chrome-48x48.png +0 -0
  267. package/src/client/public/default/android-chrome-512x512.png +0 -0
  268. package/src/client/public/default/android-chrome-72x72.png +0 -0
  269. package/src/client/public/default/android-chrome-96x96.png +0 -0
  270. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  271. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  272. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  273. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  274. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  275. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  276. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  277. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  278. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  279. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  280. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  281. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  282. package/src/client/public/default/apple-touch-icon.png +0 -0
  283. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  284. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  285. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  286. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  287. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  288. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  289. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  290. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  291. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  292. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  293. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  294. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  295. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  296. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  297. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  298. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  299. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  300. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  301. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  302. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  303. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  304. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  305. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  306. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  307. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  308. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  309. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  310. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  311. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  312. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  313. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  314. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  315. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  316. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  317. package/src/client/public/default/assets/background/white.jpg +0 -0
  318. package/src/client/public/default/browserconfig.xml +1 -1
  319. package/src/client/public/default/favicon-16x16.png +0 -0
  320. package/src/client/public/default/favicon-32x32.png +0 -0
  321. package/src/client/public/default/favicon-48x48.png +0 -0
  322. package/src/client/public/default/favicon.ico +0 -0
  323. package/src/client/public/default/manifest.webmanifest +5 -5
  324. package/src/client/public/default/mstile-144x144.png +0 -0
  325. package/src/client/public/default/mstile-150x150.png +0 -0
  326. package/src/client/public/default/mstile-310x150.png +0 -0
  327. package/src/client/public/default/mstile-310x310.png +0 -0
  328. package/src/client/public/default/mstile-70x70.png +0 -0
  329. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  330. package/src/client/public/default/yandex-browser-manifest.json +1 -1
  331. package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
  332. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
  333. package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
  334. package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
  335. package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
  336. package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
  337. package/src/client/services/object-layer/object-layer.management.js +6 -10
  338. package/src/client/services/object-layer/object-layer.service.js +3 -1
  339. package/src/client/ssr/RootDocument.js +1 -1
  340. package/src/client/ssr/body/404.js +1 -1
  341. package/src/client/ssr/body/500.js +1 -1
  342. package/src/client/ssr/body/CacheControl.js +1 -1
  343. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
  344. package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
  345. package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
  346. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
  347. package/src/client/ssr/head/CryptokoynScripts.js +1 -1
  348. package/src/client/ssr/head/Css.js +1 -1
  349. package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
  350. package/src/client/ssr/head/DefaultScripts.js +1 -1
  351. package/src/client/ssr/head/ItemledgerScripts.js +1 -1
  352. package/src/client/ssr/head/Microdata.js +1 -1
  353. package/src/client/ssr/head/Production.js +1 -1
  354. package/src/client/ssr/head/Pwa.js +1 -1
  355. package/src/client/ssr/head/PwaItemledger.js +1 -1
  356. package/src/client/ssr/head/Seo.js +1 -1
  357. package/src/client/ssr/head/UnderpostScripts.js +1 -1
  358. package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
  359. package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
  360. package/src/client/ssr/views/Cyberia404.js +7 -5
  361. package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
  362. package/src/client/ssr/views/Maintenance.js +1 -1
  363. package/src/client/ssr/views/NoNetworkConnection.js +1 -1
  364. package/src/client/ssr/views/Test.js +1 -1
  365. package/src/client/sw/core.sw.js +20 -14
  366. package/src/client-builder/client-build-docs.js +92 -77
  367. package/src/client-builder/client-build-live.js +2 -2
  368. package/src/client-builder/client-build.js +105 -74
  369. package/src/client-builder/client-bundle.js +347 -0
  370. package/src/client-builder/client-dev-server.js +3 -3
  371. package/src/client-builder/client-icons.js +2 -2
  372. package/src/client-builder/ssr.js +3 -3
  373. package/src/client.build.js +3 -6
  374. package/src/client.dev.js +3 -6
  375. package/src/db/DataBaseProvider.js +186 -3
  376. package/src/db/mariadb/MariaDB.js +3 -2
  377. package/src/db/mongo/MongoBootstrap.js +273 -101
  378. package/src/db/mongo/MongoExpress.js +187 -0
  379. package/src/db/mongo/MongooseDB.js +7 -8
  380. package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
  381. package/src/grpc/cyberia/grpc-server.js +6 -8
  382. package/src/index.js +79 -23
  383. package/src/mailer/EmailRender.js +2 -2
  384. package/src/mailer/MailerInterceptor.js +87 -0
  385. package/src/mailer/MailerProvider.js +50 -6
  386. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +144 -147
  387. package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
  388. package/src/projects/cyberia/besu-genesis-generator.js +8 -17
  389. package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
  390. package/src/projects/cyberia/catalog-cyberia.js +54 -10
  391. package/src/projects/cyberia/gemini-client.js +4 -4
  392. package/src/projects/cyberia/generate-saga.js +5 -12
  393. package/src/projects/cyberia/hot-reload-trigger.js +18 -29
  394. package/src/projects/cyberia/instance-backup.js +210 -0
  395. package/src/projects/cyberia/instance-data.js +178 -258
  396. package/src/projects/cyberia/ipfs-client.js +4 -4
  397. package/src/projects/cyberia/map-preview-generator.js +33 -69
  398. package/src/projects/cyberia/object-layer.js +153 -320
  399. package/src/projects/cyberia/seed-audio.js +139 -0
  400. package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
  401. package/src/projects/cyberia/semantic-layer-generator.js +3 -19
  402. package/src/projects/cyberia/stat-balance.js +81 -0
  403. package/src/projects/cyberia/stat-commands.js +23 -0
  404. package/src/projects/cyberia/stat-contract-generator.js +101 -0
  405. package/src/projects/underpost/catalog-underpost.js +9 -1
  406. package/src/proxy.js +4 -7
  407. package/src/runtime/cyberia-client/Dockerfile +17 -8
  408. package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
  409. package/src/runtime/cyberia-server/Dockerfile +18 -6
  410. package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
  411. package/src/runtime/engine-cyberia/Dockerfile +25 -9
  412. package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
  413. package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
  414. package/src/runtime/engine-cyberia/compose.env +17 -5
  415. package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
  416. package/src/runtime/express/Express.js +32 -27
  417. package/src/runtime/lampp/Lampp.js +22 -70
  418. package/src/runtime/nginx/Nginx.js +1 -1
  419. package/src/runtime/wp/Wp.js +16 -48
  420. package/src/server/{catalog.js → build/catalog.js} +15 -5
  421. package/src/server/build/coverage.js +188 -0
  422. package/src/server/build/execution.js +402 -0
  423. package/src/server/build/package.js +508 -0
  424. package/src/server/build/testing.js +578 -0
  425. package/src/server/{dns.js → network/dns.js} +144 -11
  426. package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
  427. package/src/server/{middlewares.js → network/middlewares.js} +11 -5
  428. package/src/server/network/node-capability.js +98 -0
  429. package/src/server/{peer.js → network/peer.js} +3 -3
  430. package/src/server/{proxy.js → network/proxy.js} +7 -13
  431. package/src/server/network/router.js +356 -0
  432. package/src/server/{tls.js → network/tls.js} +2 -2
  433. package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
  434. package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
  435. package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
  436. package/src/server/{cri.js → ops/cri.js} +2 -2
  437. package/src/server/{cron.js → ops/cron.js} +349 -74
  438. package/src/server/ops/event-notification.js +284 -0
  439. package/src/server/{logger.js → ops/logger.js} +143 -23
  440. package/src/server/ops/monitoring.js +1724 -0
  441. package/src/server/{systemd.js → ops/systemd.js} +55 -1
  442. package/src/server/{conf.js → runtime/conf.js} +516 -423
  443. package/src/server/runtime/config-scope.js +215 -0
  444. package/src/server/runtime/environment.js +149 -0
  445. package/src/server/{process.js → runtime/process.js} +153 -12
  446. package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
  447. package/src/server/{runtime.js → runtime/runtime.js} +12 -8
  448. package/src/server/{start.js → runtime/start.js} +128 -39
  449. package/src/server/{auth.js → security/auth.js} +7 -8
  450. package/src/server/security/container-storage.js +382 -0
  451. package/src/server/{crypto.js → security/crypto.js} +1 -1
  452. package/src/server/{selinux.js → security/selinux.js} +1 -1
  453. package/src/server/{backup.js → storage/backup.js} +17 -8
  454. package/src/server/{data-query.js → storage/data-query.js} +17 -1
  455. package/src/server/storage/downloader.js +121 -0
  456. package/src/server/storage/repository.js +68 -0
  457. package/src/server/storage/zip.js +153 -0
  458. package/src/server.js +4 -7
  459. package/src/ws/IoInterface.js +1 -1
  460. package/src/ws/IoServer.js +1 -1
  461. package/src/ws/core/core.ws.connection.js +1 -1
  462. package/src/ws/core/core.ws.emit.js +1 -1
  463. package/src/ws/core/core.ws.server.js +1 -1
  464. package/src/ws/default/default.ws.connection.js +1 -1
  465. package/src/ws/default/default.ws.emit.js +1 -1
  466. package/src/ws/default/default.ws.server.js +1 -1
  467. package/test/e2e/event-e2e-public-ingress-down.js +45 -0
  468. package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
  469. package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
  470. package/test/{api.test.js → integration/app/api.test.js} +33 -14
  471. package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
  472. package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
  473. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +64 -0
  474. package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
  475. package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
  476. package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
  477. package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
  478. package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
  479. package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
  480. package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
  481. package/test/integration/app/cyberia/fallback-world-capture.test.js +338 -0
  482. package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
  483. package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
  484. package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
  485. package/test/integration/app/cyberia/seed-audio.test.js +275 -0
  486. package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
  487. package/test/integration/infra/1-security/config-scope.test.js +305 -0
  488. package/test/integration/infra/1-security/container-storage.test.js +356 -0
  489. package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
  490. package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
  491. package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
  492. package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
  493. package/test/integration/infra/1-security/systemd-service.test.js +70 -0
  494. package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
  495. package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
  496. package/test/integration/infra/2-network/node-capability.test.js +182 -0
  497. package/test/integration/infra/2-network/wireguard-cli.test.js +1187 -0
  498. package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
  499. package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
  500. package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
  501. package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
  502. package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
  503. package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
  504. package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
  505. package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
  506. package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
  507. package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
  508. package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
  509. package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
  510. package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
  511. package/test/integration/infra/5-observability/event-notification.test.js +197 -0
  512. package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
  513. package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
  514. package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
  515. package/test/support/shell-harness.js +73 -0
  516. package/test/unit/build-template.test.js +97 -0
  517. package/test/unit/build-workflow-offline.test.js +132 -0
  518. package/test/unit/catalog.test.js +71 -0
  519. package/test/unit/client-build-docs.test.js +361 -0
  520. package/test/unit/client-bundle.test.js +313 -0
  521. package/test/unit/conf-loading.test.js +303 -0
  522. package/test/unit/conf-resolution.test.js +996 -0
  523. package/test/unit/coverage-artifact.test.js +221 -0
  524. package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
  525. package/test/unit/cyberia/instance-backup.test.js +183 -0
  526. package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
  527. package/test/unit/cyberia/publish-workflow.test.js +22 -0
  528. package/test/unit/cyberia/stat-balance.test.js +69 -0
  529. package/test/unit/deploy-env-resolution.test.js +38 -0
  530. package/test/unit/deploy-log-table.test.js +518 -0
  531. package/test/unit/downloader-integrity.test.js +98 -0
  532. package/test/unit/execution-profiles.test.js +219 -0
  533. package/test/unit/file-reference-registry.test.js +141 -0
  534. package/test/unit/fleet-sync-source.test.js +149 -0
  535. package/test/unit/fs-storage-paths.test.js +596 -0
  536. package/test/unit/logger-redaction.test.js +141 -0
  537. package/test/unit/package.test.js +457 -0
  538. package/test/unit/prepare-host.test.js +382 -0
  539. package/test/unit/process-environment.test.js +18 -0
  540. package/test/unit/propagation-message.test.js +78 -0
  541. package/test/unit/redeploy-plan.test.js +24 -0
  542. package/test/unit/release-bump.test.js +474 -0
  543. package/test/unit/ssh-output-redaction.test.js +28 -0
  544. package/test/unit/start-options.test.js +46 -0
  545. package/test/unit/test-tiers.test.js +220 -0
  546. package/test/unit/zip-archive.test.js +142 -0
  547. package/vitest.config.js +66 -0
  548. package/.nycrc +0 -9
  549. package/deploy/lib/logging.sh +0 -96
  550. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
  551. package/manifests/mongodb/pv-pvc.yaml +0 -59
  552. package/scripts/coverall-test-cyberia.sh +0 -24
  553. package/scripts/coverall-test.sh +0 -24
  554. package/scripts/link-local-underpost-cli.sh +0 -6
  555. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
  556. package/src/api/user/user.build.js +0 -16
  557. package/src/cli/env.js +0 -177
  558. package/src/client/components/core/Blockchain.js +0 -41
  559. package/src/client/components/core/Blog.js +0 -9
  560. package/src/client/components/core/KeyboardAvoidance.js +0 -145
  561. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
  562. package/src/client/public/default/assets/background/dark.jpg +0 -0
  563. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  564. package/src/client/public/default/assets/background/white0.jpg +0 -0
  565. package/src/client/public/doc/favicon.ico +0 -0
  566. package/src/client/public/doc/sitemap +0 -148
  567. package/src/server/downloader.js +0 -73
  568. package/src/server/environment.js +0 -98
@@ -15,9 +15,9 @@ import {
15
15
  loadConfInstances,
16
16
  loadProjectInstanceEnvBuilder,
17
17
  normalizeInstanceTopology,
18
- } from '../src/server/conf.js';
19
- import { statusPageAssetPathFactory } from '../src/server/underpost-gateway.js';
20
- import UnderpostDockerCompose from '../src/cli/docker-compose.js';
18
+ } from '../../../../src/server/runtime/conf.js';
19
+ import { statusPageAssetPathFactory } from '../../../../src/server/network/underpost-gateway.js';
20
+ import UnderpostDockerCompose from '../../../../src/cli/docker-compose.js';
21
21
 
22
22
  // `clusterInstancesFactory` reads `./engine-private/conf/<deployId>/conf.instances.json`
23
23
  // relative to the process cwd, mirroring every other conf loader. engine-private
@@ -61,7 +61,7 @@ const FIXTURES = {
61
61
  describe('cluster custom instances', () => {
62
62
  const created = [];
63
63
 
64
- before(() => {
64
+ beforeAll(() => {
65
65
  for (const [deployId, entries] of Object.entries(FIXTURES)) {
66
66
  const dir = CONF_DIR(deployId);
67
67
  if (fs.existsSync(dir)) throw new Error(`Refusing to write fixtures into an existing deploy: ${dir}`);
@@ -75,7 +75,7 @@ describe('cluster custom instances', () => {
75
75
  fs.outputFileSync(`${customComposeDir}/project-router.conf`, 'project-owned\n');
76
76
  });
77
77
 
78
- after(() => {
78
+ afterAll(() => {
79
79
  for (const dir of created) fs.removeSync(dir);
80
80
  });
81
81
 
@@ -158,12 +158,8 @@ describe('cluster custom instances', () => {
158
158
  const hostsPath = `${customComposeDir}/hosts`;
159
159
  fs.writeFileSync(hostsPath, '127.0.0.1 localhost\n', 'utf8');
160
160
  const options = { path: hostsPath, append: true, blockId: 'fixture-docker-compose' };
161
- expect(etcHostFactory(['fixture-client', 'fixture-server', 'fixture-engine'], options).changed).to.equal(
162
- true,
163
- );
164
- expect(etcHostFactory(['fixture-client', 'fixture-server', 'fixture-engine'], options).changed).to.equal(
165
- false,
166
- );
161
+ expect(etcHostFactory(['fixture-client', 'fixture-server', 'fixture-engine'], options).changed).to.equal(true);
162
+ expect(etcHostFactory(['fixture-client', 'fixture-server', 'fixture-engine'], options).changed).to.equal(false);
167
163
  const hosts = fs.readFileSync(hostsPath, 'utf8');
168
164
  expect(hosts.match(/underpost hosts fixture-docker-compose:begin/g)).to.have.length(1);
169
165
  expect(hosts).to.include('127.0.0.1 localhost');
@@ -184,7 +180,7 @@ describe('cluster custom instances', () => {
184
180
  it('resolves several instances across several deploys', () => {
185
181
  const { byDeployId, unmatched } = clusterInstancesFactory(
186
182
  ['dd-fixture-a', 'dd-fixture-b'],
187
- 'mmo-server+mmo-client+worker',
183
+ 'mmo-server,mmo-client,worker',
188
184
  );
189
185
  expect(byDeployId['dd-fixture-a'].ids).to.deep.equal(['mmo-server', 'mmo-client']);
190
186
  expect(byDeployId['dd-fixture-a'].hosts).to.deep.equal(['server.fixture.test', 'client.fixture.test']);
@@ -207,13 +203,13 @@ describe('cluster custom instances', () => {
207
203
  });
208
204
 
209
205
  it('reports an id no deploy declares instead of silently skipping it', () => {
210
- const { byDeployId, unmatched } = clusterInstancesFactory(['dd-fixture-a'], 'mmo-server+nope');
206
+ const { byDeployId, unmatched } = clusterInstancesFactory(['dd-fixture-a'], 'mmo-server,nope');
211
207
  expect(byDeployId['dd-fixture-a'].ids).to.deep.equal(['mmo-server']);
212
208
  expect(unmatched).to.deep.equal(['nope']);
213
209
  });
214
210
 
215
211
  it('is a no-op when no instance list is given', () => {
216
- for (const list of ['', undefined, '+'])
212
+ for (const list of ['', undefined, ','])
217
213
  expect(clusterInstancesFactory(['dd-fixture-a'], list)).to.deep.equal({
218
214
  byDeployId: { 'dd-fixture-a': { ids: [], hosts: [] } },
219
215
  unmatched: [],
@@ -311,7 +307,7 @@ describe('cluster custom instances', () => {
311
307
  });
312
308
 
313
309
  describe('cluster gateway bootstrap order', () => {
314
- const source = fs.readFileSync(new URL('../src/cli/run.js', import.meta.url), 'utf8');
310
+ const source = fs.readFileSync(new URL('../../../../src/cli/run.js', import.meta.url), 'utf8');
315
311
  const clusterRunner = source.slice(source.indexOf('cluster: async'), source.indexOf("'gateway-status': async"));
316
312
 
317
313
  it('tests the ingress-only fallback before applying any workload Deployment', () => {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { expect } from 'chai';
4
4
  import fs from 'fs-extra';
5
- import Underpost from '../src/index.js';
5
+ import Underpost from '../../../../src/index.js';
6
6
 
7
7
  const deploymentManifest = (nodeName = '') =>
8
8
  Underpost.deploy.deploymentYamlPartsFactory({
@@ -29,7 +29,7 @@ describe('deployment node placement', () => {
29
29
  });
30
30
 
31
31
  it('does not restart a controller after node-move patches its pod template', () => {
32
- const source = fs.readFileSync(new URL('../src/cli/run.js', import.meta.url), 'utf8');
32
+ const source = fs.readFileSync(new URL('../../../../src/cli/run.js', import.meta.url), 'utf8');
33
33
  const start = source.indexOf(" 'node-move':");
34
34
  const end = source.indexOf('\n /**', start);
35
35
  const runner = source.slice(start, end);
@@ -38,7 +38,7 @@ describe('deployment node placement', () => {
38
38
  });
39
39
 
40
40
  it('forwards --node-name into live and generated custom-instance manifests', () => {
41
- const source = fs.readFileSync(new URL('../src/cli/run.js', import.meta.url), 'utf8');
41
+ const source = fs.readFileSync(new URL('../../../../src/cli/run.js', import.meta.url), 'utf8');
42
42
  const placements = source.match(/nodeName: options\.nodeName\s*\? Underpost\.deploy\.resolveDeployNode/g) || [];
43
43
  expect(placements).to.have.length(2);
44
44
  });
@@ -0,0 +1,313 @@
1
+ 'use strict';
2
+
3
+ import { expect } from 'chai';
4
+ import fs from 'fs-extra';
5
+ import UnderpostDockerCompose from '../../../../src/cli/docker-compose.js';
6
+ import { shellHarness } from '../../../support/shell-harness.js';
7
+
8
+ // Everything below writes generated artifacts under the engine tree and drives
9
+ // the docker CLI. Both are replaced: an in-memory file table for the artifacts,
10
+ // and the shell harness for the compose invocations.
11
+ const composeFixture = (files = {}) => {
12
+ const table = new Map(Object.entries(files));
13
+ const written = new Map();
14
+ const removed = [];
15
+ const keys = () => [...table.keys(), ...written.keys()];
16
+
17
+ vi.spyOn(fs, 'existsSync').mockImplementation((filePath) => {
18
+ const key = `${filePath}`;
19
+ if (removed.includes(key)) return false;
20
+ return table.has(key) || written.has(key) || keys().some((entry) => entry.startsWith(`${key}/`));
21
+ });
22
+ vi.spyOn(fs, 'readFileSync').mockImplementation(
23
+ (filePath) => table.get(`${filePath}`) ?? written.get(`${filePath}`) ?? '',
24
+ );
25
+ vi.spyOn(fs, 'writeFileSync').mockImplementation((filePath, value) => written.set(`${filePath}`, `${value}`));
26
+ vi.spyOn(fs, 'mkdirpSync').mockImplementation(() => undefined);
27
+ vi.spyOn(fs, 'copySync').mockImplementation((src, dest) =>
28
+ written.set(`${dest}`, written.get(`${src}`) ?? table.get(`${src}`) ?? ''),
29
+ );
30
+ vi.spyOn(fs, 'removeSync').mockImplementation((filePath) => {
31
+ written.delete(`${filePath}`);
32
+ removed.push(`${filePath}`);
33
+ });
34
+ return { written, removed };
35
+ };
36
+
37
+ const relative = (path) => `${path}`.replace(`${process.cwd()}/`, '');
38
+
39
+ describe('docker compose stack', () => {
40
+ let harness;
41
+
42
+ beforeEach(() => {
43
+ harness = shellHarness();
44
+ });
45
+
46
+ afterEach(() => {
47
+ harness.restore();
48
+ vi.restoreAllMocks();
49
+ });
50
+
51
+ describe('generated artifacts', () => {
52
+ it('renders the nginx routes, mongo entrypoint, monitoring config and env example', () => {
53
+ const { written } = composeFixture();
54
+ UnderpostDockerCompose.generate({});
55
+ const paths = [...written.keys()].map(relative);
56
+ expect(paths).to.include('docker/nginx/default.conf');
57
+ expect(paths).to.include('docker/mongodb/entrypoint.sh');
58
+ expect(paths).to.include('docker/prometheus/prometheus.yml');
59
+ expect(paths).to.include('docker/grafana/provisioning/datasources/datasource.yml');
60
+ expect(paths).to.include('docker/compose.env.example');
61
+ expect(paths).to.include('docker/compose.app.yml');
62
+ });
63
+
64
+ it('seeds the working env-file from the example only when it is absent', () => {
65
+ const { written } = composeFixture();
66
+ UnderpostDockerCompose.generate({});
67
+ const envPath = [...written.keys()].find((path) => relative(path) === 'docker/compose.env');
68
+ expect(written.get(envPath)).to.equal(written.get(`${envPath}.example`));
69
+
70
+ vi.restoreAllMocks();
71
+ const existing = composeFixture({ [envPath]: 'DB_PASSWORD=real\n' });
72
+ UnderpostDockerCompose.generate({});
73
+ expect(existing.written.has(envPath)).to.equal(false);
74
+ });
75
+
76
+ it('bakes the deploy id and environment into the app command override', () => {
77
+ const { written } = composeFixture();
78
+ UnderpostDockerCompose.generate({ deployId: 'dd-core', env: 'production' });
79
+ const override = written.get([...written.keys()].find((path) => relative(path) === 'docker/compose.app.yml'));
80
+ expect(override).to.include('dd-core');
81
+ expect(override).to.include('production');
82
+ });
83
+
84
+ it('self-bootstraps a fresh engine for the default deploy', () => {
85
+ expect(UnderpostDockerCompose.appCommand().join(' ')).to.include('underpost new engine');
86
+ expect(UnderpostDockerCompose.appCommand('dd-core', 'production').join(' ')).to.include(
87
+ 'underpost start --build --run dd-core production',
88
+ );
89
+ });
90
+
91
+ it('honours every generated path override', () => {
92
+ const { written } = composeFixture();
93
+ UnderpostDockerCompose.generate({
94
+ nginxConf: 'custom/nginx.conf',
95
+ envFile: 'custom/env',
96
+ appOverride: 'custom/app.yml',
97
+ });
98
+ const paths = [...written.keys()].map(relative);
99
+ expect(paths).to.include('custom/nginx.conf');
100
+ expect(paths).to.include('custom/env');
101
+ expect(paths).to.include('custom/app.yml');
102
+ });
103
+
104
+ it('scrapes the app service in the rendered prometheus config', () => {
105
+ expect(UnderpostDockerCompose.prometheusContent()).to.include('app:4001');
106
+ expect(UnderpostDockerCompose.grafanaDatasourceContent()).to.include('http://prometheus:9090');
107
+ expect(UnderpostDockerCompose.envExampleContent()).to.include('=');
108
+ expect(UnderpostDockerCompose.mongoEntrypointContent()).to.include('BOOTSTRAP_USER_CREATED');
109
+ });
110
+
111
+ // A named workflow is fully owned by its canonical directory: the generic
112
+ // CLI never writes application-specific content into it.
113
+ it('uses a custom workflow canonical files as-is', () => {
114
+ const base = 'engine-private/conf/dd-core/docker-compose/custom';
115
+ const { written } = composeFixture({
116
+ [UnderpostDockerCompose.resolve(`${base}/docker-compose.yml`)]: 'services: {}\n',
117
+ [UnderpostDockerCompose.resolve(`${base}/compose.env`)]: 'FIXTURE=true\n',
118
+ });
119
+ UnderpostDockerCompose.generate({ deployId: 'dd-core', dockerComposeId: 'custom' });
120
+ expect(written.size).to.equal(0);
121
+ });
122
+
123
+ it('names the canonical files a custom workflow is missing', () => {
124
+ composeFixture();
125
+ expect(() => UnderpostDockerCompose.generate({ deployId: 'dd-core', dockerComposeId: 'custom' })).to.throw(
126
+ 'missing',
127
+ );
128
+ });
129
+ });
130
+
131
+ describe('host installation', () => {
132
+ it('skips a platform the installer does not target', () => {
133
+ const previous = Object.getOwnPropertyDescriptor(process, 'platform');
134
+ Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
135
+ try {
136
+ UnderpostDockerCompose.install({});
137
+ expect(harness.calls.length).to.equal(0);
138
+ } finally {
139
+ Object.defineProperty(process, 'platform', previous);
140
+ }
141
+ });
142
+
143
+ it('installs nothing when compose already works', () => {
144
+ composeFixture({ '/etc/redhat-release': 'Rocky Linux release 9\n' });
145
+ harness.route({ match: 'docker compose version', code: 0, stdout: 'v2.29.0\n' });
146
+ UnderpostDockerCompose.install({});
147
+ expect(harness.ran('dnf -y install docker-ce')).to.equal(false);
148
+ });
149
+
150
+ it('installs the engine and the compose plugin from the official repository', () => {
151
+ composeFixture({ '/etc/redhat-release': 'Rocky Linux release 9\n' });
152
+ harness.route({ match: 'docker compose version', code: 1, stdout: '' });
153
+ UnderpostDockerCompose.install({ user: 'operator' });
154
+ expect(harness.ran('download.docker.com/linux/rhel/docker-ce.repo')).to.equal(true);
155
+ expect(harness.ran('dnf -y install docker-ce docker-ce-cli containerd.io')).to.equal(true);
156
+ expect(harness.ran('systemctl enable --now docker')).to.equal(true);
157
+ expect(harness.ran('usermod -aG docker operator')).to.equal(true);
158
+ });
159
+
160
+ it('reinstalls under --force and warns on a host that is not RHEL-compatible', () => {
161
+ composeFixture();
162
+ harness.route({ match: 'docker compose version', code: 0, stdout: 'v2.29.0\n' });
163
+ UnderpostDockerCompose.install({ force: true, user: 'root' });
164
+ expect(harness.ran('dnf -y install docker-ce')).to.equal(true);
165
+ // root is already privileged; adding it to the group buys nothing.
166
+ expect(harness.ran('usermod -aG docker')).to.equal(false);
167
+ });
168
+ });
169
+
170
+ describe('reset', () => {
171
+ it('tears down containers, volumes and images, then prunes the generated artifacts', () => {
172
+ const { removed } = composeFixture({
173
+ [UnderpostDockerCompose.resolve('docker/nginx/default.conf')]: 'server {}\n',
174
+ [UnderpostDockerCompose.resolve('docker/compose.env')]: 'DB_PASSWORD=real\n',
175
+ });
176
+ UnderpostDockerCompose.reset({});
177
+ expect(harness.ran('down --remove-orphans --volumes --rmi local')).to.equal(true);
178
+ expect(removed.map(relative)).to.include('docker/nginx/default.conf');
179
+ // The working env-file holds credentials, so it survives a plain reset.
180
+ expect(removed.map(relative)).not.to.include('docker/compose.env');
181
+ });
182
+
183
+ it('drops the working env-file too under --force', () => {
184
+ const { removed } = composeFixture({ [UnderpostDockerCompose.resolve('docker/compose.env')]: 'DB=1\n' });
185
+ UnderpostDockerCompose.reset({ force: true });
186
+ expect(removed.map(relative)).to.include('docker/compose.env');
187
+ });
188
+
189
+ it('never prunes the files a custom workflow owns', () => {
190
+ const base = 'engine-private/conf/dd-core/docker-compose/custom';
191
+ const { removed } = composeFixture({
192
+ [UnderpostDockerCompose.resolve(`${base}/docker-compose.yml`)]: 'services: {}\n',
193
+ [UnderpostDockerCompose.resolve(`${base}/compose.env`)]: 'FIXTURE=true\n',
194
+ });
195
+ UnderpostDockerCompose.reset({ deployId: 'dd-core', dockerComposeId: 'custom' });
196
+ expect(harness.ran('down --remove-orphans')).to.equal(true);
197
+ expect(removed).to.deep.equal([]);
198
+ });
199
+ });
200
+
201
+ describe('CLI dispatch', () => {
202
+ const run = async (target, options) => {
203
+ composeFixture();
204
+ await UnderpostDockerCompose.API.callback(target, options);
205
+ };
206
+
207
+ it('brings the stack up when no action flag is given', async () => {
208
+ await run('', {});
209
+ expect(harness.ran('up -d')).to.equal(true);
210
+ });
211
+
212
+ it('rebuilds the images when asked to', async () => {
213
+ await run('', { up: true, build: true });
214
+ expect(harness.ran('up -d --build')).to.equal(true);
215
+ });
216
+
217
+ // baseCmd conditionally includes the app override, so it has to exist before
218
+ // the invocation is composed — notably after a reset prunes it.
219
+ it('regenerates the config before composing the invocation', async () => {
220
+ const { written } = composeFixture();
221
+ let generatedBeforeUp = false;
222
+ harness.route({
223
+ match: (command) => {
224
+ if (command.includes('up -d')) generatedBeforeUp = written.size > 0;
225
+ return false;
226
+ },
227
+ });
228
+ await UnderpostDockerCompose.API.callback('', { up: true });
229
+ expect(generatedBeforeUp).to.equal(true);
230
+ });
231
+
232
+ it('stops at the install when nothing else was requested', async () => {
233
+ composeFixture({ '/etc/redhat-release': 'Rocky\n' });
234
+ harness.route({ match: 'docker compose version', code: 0, stdout: 'v2\n' });
235
+ await UnderpostDockerCompose.API.callback('', { install: true });
236
+ expect(harness.ran('up -d')).to.equal(false);
237
+ });
238
+
239
+ it('continues past the install when a lifecycle flag follows it', async () => {
240
+ composeFixture({ '/etc/redhat-release': 'Rocky\n' });
241
+ harness.route({ match: 'docker compose version', code: 0, stdout: 'v2\n' });
242
+ await UnderpostDockerCompose.API.callback('', { install: true, up: true });
243
+ expect(harness.ran('up -d')).to.equal(true);
244
+ });
245
+
246
+ it('stops at the reset when nothing else was requested', async () => {
247
+ await run('', { reset: true });
248
+ expect(harness.ran('up -d')).to.equal(false);
249
+ });
250
+
251
+ it('recreates the stack when a reset is followed by an up', async () => {
252
+ await run('', { reset: true, up: true });
253
+ expect(harness.ran('up -d')).to.equal(true);
254
+ });
255
+
256
+ it('routes every remaining lifecycle flag to its compose subcommand', async () => {
257
+ for (const [options, expected] of [
258
+ [{ down: true }, 'down --remove-orphans'],
259
+ [{ down: true, volumes: true }, 'down --remove-orphans --volumes'],
260
+ [{ restart: true }, 'restart'],
261
+ [{ build: true }, 'build --no-cache'],
262
+ [{ pull: true }, 'pull'],
263
+ [{ logs: true }, 'logs -f --tail=200'],
264
+ [{ status: true }, 'ps --format'],
265
+ [{ exec: 'config' }, 'config'],
266
+ ]) {
267
+ harness.restore();
268
+ harness = shellHarness();
269
+ await run('', options);
270
+ expect(harness.ran(expected), JSON.stringify(options)).to.equal(true);
271
+ }
272
+ });
273
+
274
+ it('targets a single service where one is named', async () => {
275
+ await run('app', { logs: true });
276
+ expect(harness.ran('logs -f --tail=200 app')).to.equal(true);
277
+ harness.restore();
278
+ harness = shellHarness();
279
+ await run('mongodb', { restart: true });
280
+ expect(harness.ran('restart mongodb')).to.equal(true);
281
+ });
282
+
283
+ it('opens bash in the app service and sh in every other', async () => {
284
+ await run('', { shell: true });
285
+ expect(harness.ran('exec app /bin/bash')).to.equal(true);
286
+ harness.restore();
287
+ harness = shellHarness();
288
+ await run('mongodb', { shell: true });
289
+ expect(harness.ran('exec mongodb /bin/sh')).to.equal(true);
290
+ });
291
+
292
+ it('writes the config and runs nothing for a bare generate', async () => {
293
+ await run('', { generate: true });
294
+ expect(harness.calls.length).to.equal(0);
295
+ });
296
+
297
+ it('fails the process rather than half-applying a broken invocation', async () => {
298
+ const previousExit = process.exit;
299
+ let exitCode;
300
+ process.exit = (code) => {
301
+ exitCode = code;
302
+ };
303
+ try {
304
+ composeFixture();
305
+ harness.route({ match: 'up -d', throws: new Error('docker daemon unreachable') });
306
+ await UnderpostDockerCompose.API.callback('', { up: true });
307
+ expect(exitCode).to.equal(1);
308
+ } finally {
309
+ process.exit = previousExit;
310
+ }
311
+ });
312
+ });
313
+ });
@@ -0,0 +1,156 @@
1
+ 'use strict';
2
+
3
+ import { expect } from 'chai';
4
+ import fs from 'fs-extra';
5
+ // Named import: js-yaml's ESM build exports no default.
6
+ import { load } from 'js-yaml';
7
+ import {
8
+ MONGO_EXPRESS_BASE_URL,
9
+ MONGO_EXPRESS_IMAGE,
10
+ MONGO_EXPRESS_NAME,
11
+ MONGO_EXPRESS_NODE_PORT,
12
+ MONGO_EXPRESS_PORT,
13
+ MONGO_EXPRESS_SECRET_NAME,
14
+ MONGO_EXPRESS_SERVICE_NAME,
15
+ mongoAuthEnabledFactory,
16
+ mongoExpressManifestPathFactory,
17
+ mongoExpressNodePortManifestFactory,
18
+ parseMongodArgs,
19
+ } from '../../../../src/db/mongo/MongoExpress.js';
20
+
21
+ const MONGODB_AUTH_ARGS = load(fs.readFileSync('./manifests/mongodb/statefulset.yaml', 'utf8')).spec.template.spec
22
+ .containers[0].args;
23
+ const MONGODB_4_4_ARGS = load(fs.readFileSync('./manifests/mongodb-4.4/statefulset.yaml', 'utf8')).spec.template.spec
24
+ .containers[0].args;
25
+
26
+ const readYaml = (path) => load(fs.readFileSync(path, 'utf8'));
27
+ const manifestDir = (authEnabled) => mongoExpressManifestPathFactory({ authEnabled });
28
+ const container = (deployment) => deployment.spec.template.spec.containers[0];
29
+ const envEntry = (deployment, name) => container(deployment).env.find((entry) => entry.name === name);
30
+
31
+ describe('mongo-express auth mode selection', () => {
32
+ it('reads authentication from the deployed statefulset, not from the invocation flags', () => {
33
+ // The set that is running decides, whichever flag this invocation carries.
34
+ expect(mongoAuthEnabledFactory({ args: MONGODB_AUTH_ARGS, options: { mongodb4: true } })).to.equal(true);
35
+ expect(mongoAuthEnabledFactory({ args: MONGODB_4_4_ARGS, options: { mongodb: true } })).to.equal(false);
36
+ });
37
+
38
+ it('falls back to the flags only when no statefulset is deployed', () => {
39
+ expect(mongoAuthEnabledFactory({ args: null, options: { mongodb4: true } })).to.equal(false);
40
+ expect(mongoAuthEnabledFactory({ args: null, options: { mongodb: true } })).to.equal(true);
41
+ expect(mongoAuthEnabledFactory({})).to.equal(true);
42
+ });
43
+
44
+ it('separates an undeployed statefulset from one that declares no arguments', () => {
45
+ expect(parseMongodArgs('')).to.equal(null);
46
+ expect(parseMongodArgs(' ')).to.equal(null);
47
+ expect(parseMongodArgs('mongodb|--replSet|rs0|--auth|')).to.deep.equal(['--replSet', 'rs0', '--auth']);
48
+ // An empty vector is a deployed server running on its defaults: no --auth, so no credentials,
49
+ // and the flags of this invocation do not get to overrule it.
50
+ expect(mongoAuthEnabledFactory({ args: parseMongodArgs('mongodb|'), options: { mongodb: true } })).to.equal(false);
51
+ expect(mongoAuthEnabledFactory({ args: parseMongodArgs(''), options: { mongodb: true } })).to.equal(true);
52
+ });
53
+
54
+ it('routes each auth mode to a kustomization that exists', () => {
55
+ for (const authEnabled of [true, false]) {
56
+ const path = manifestDir(authEnabled);
57
+ expect(fs.existsSync(`${path}/kustomization.yaml`), path).to.equal(true);
58
+ }
59
+ expect(manifestDir(true)).to.not.equal(manifestDir(false));
60
+ expect(mongoExpressManifestPathFactory({ underpostRoot: '/npm/underpost', authEnabled: true })).to.equal(
61
+ '/npm/underpost/manifests/deployment/mongo-express',
62
+ );
63
+ });
64
+ });
65
+
66
+ describe('mongo-express manifests', () => {
67
+ const base = manifestDir(true);
68
+ const deployment = readYaml(`${base}/deployment.yaml`);
69
+ const service = readYaml(`${base}/service.yaml`);
70
+ const kustomization = readYaml(`${base}/kustomization.yaml`);
71
+
72
+ it('projects both credential pairs from the statefulset secret', () => {
73
+ // The client owns no credential of its own: the same secret the StatefulSet consumes gates
74
+ // the mongod connection and the UI session.
75
+ for (const name of [
76
+ 'ME_CONFIG_MONGODB_ADMINUSERNAME',
77
+ 'ME_CONFIG_MONGODB_ADMINPASSWORD',
78
+ 'ME_CONFIG_BASICAUTH_USERNAME',
79
+ 'ME_CONFIG_BASICAUTH_PASSWORD',
80
+ ]) {
81
+ const entry = envEntry(deployment, name);
82
+ expect(entry, name).to.be.an('object');
83
+ expect(entry.value, name).to.equal(undefined);
84
+ expect(entry.valueFrom.secretKeyRef.name, name).to.equal(MONGO_EXPRESS_SECRET_NAME);
85
+ expect(entry.valueFrom.secretKeyRef.key, name).to.be.oneOf(['username', 'password']);
86
+ }
87
+ });
88
+
89
+ it('targets the in-cluster statefulset identity, which is identical under kind, kubeadm and k3s', () => {
90
+ expect(envEntry(deployment, 'ME_CONFIG_MONGODB_SERVER').value).to.equal('mongodb-0.mongodb-service');
91
+ expect(envEntry(deployment, 'ME_CONFIG_MONGODB_PORT').value).to.equal('27017');
92
+ expect(envEntry(deployment, 'ME_CONFIG_MONGODB_ENABLE_ADMIN').value).to.equal('true');
93
+ // The deploy surface reports the URL operators open; the manifest decides what it is.
94
+ expect(envEntry(deployment, 'ME_CONFIG_SITE_BASEURL').value).to.equal(MONGO_EXPRESS_BASE_URL);
95
+ });
96
+
97
+ it('pins the image the pull path preloads', () => {
98
+ expect(container(deployment).image).to.equal(MONGO_EXPRESS_IMAGE);
99
+ expect(MONGO_EXPRESS_IMAGE).to.match(/:.+$/);
100
+ });
101
+
102
+ it('keeps the service names and ports the deploy surface reports', () => {
103
+ expect(deployment.metadata.name).to.equal(MONGO_EXPRESS_NAME);
104
+ expect(service.metadata.name).to.equal(MONGO_EXPRESS_SERVICE_NAME);
105
+ expect(service.spec.type).to.equal('ClusterIP');
106
+ expect(service.spec.ports[0].port).to.equal(MONGO_EXPRESS_PORT);
107
+ expect(container(deployment).ports[0].containerPort).to.equal(MONGO_EXPRESS_PORT);
108
+ });
109
+
110
+ it('leaves node exposure out of the kustomization', () => {
111
+ // An admin UI reaches the node network only through `--node-port`.
112
+ const nodePortManifest = mongoExpressNodePortManifestFactory({});
113
+ expect(kustomization.resources).to.deep.equal(['deployment.yaml', 'service.yaml']);
114
+ const nodePortService = readYaml(nodePortManifest);
115
+ expect(nodePortService.spec.type).to.equal('NodePort');
116
+ expect(nodePortService.spec.ports[0].nodePort).to.equal(MONGO_EXPRESS_NODE_PORT);
117
+ expect(nodePortService.spec.selector).to.deep.equal(deployment.spec.selector.matchLabels);
118
+ });
119
+
120
+ it('drops only the credentials a no-auth mongod would reject', () => {
121
+ const overlay = manifestDir(false);
122
+ const overlayKustomization = readYaml(`${overlay}/kustomization.yaml`);
123
+ expect(overlayKustomization.resources).to.deep.equal(['../mongo-express']);
124
+ const patchPath = `${overlay}/${overlayKustomization.patches[0].path}`;
125
+ const deleted = readYaml(patchPath)
126
+ .spec.template.spec.containers[0].env.filter((entry) => entry.$patch === 'delete')
127
+ .map((entry) => entry.name);
128
+ expect(deleted).to.deep.equal([
129
+ 'ME_CONFIG_MONGODB_ADMINUSERNAME',
130
+ 'ME_CONFIG_MONGODB_ADMINPASSWORD',
131
+ 'ME_CONFIG_MONGODB_AUTH_DATABASE',
132
+ ]);
133
+ // The UI session stays gated: dropping the server credentials must not publish the client.
134
+ expect(deleted).to.not.include('ME_CONFIG_BASICAUTH_USERNAME');
135
+ expect(deleted).to.not.include('ME_CONFIG_BASICAUTH_PASSWORD');
136
+ });
137
+ });
138
+
139
+ describe('mongo-express cluster wiring', () => {
140
+ const clusterSource = fs.readFileSync('./src/cli/cluster.js', 'utf8');
141
+
142
+ it('registers the flag on the cluster command', () => {
143
+ expect(fs.readFileSync('./src/cli/index.js', 'utf8')).to.include("'--mongo-express'");
144
+ });
145
+
146
+ it('deploys the client after the statefulset branches', () => {
147
+ expect(clusterSource).to.include('if (options.mongoExpress)');
148
+ expect(clusterSource.indexOf('options.mongoExpress')).to.be.greaterThan(clusterSource.indexOf('options.mongodb4'));
149
+ });
150
+
151
+ it('keeps one deploy path shared with the service runner', () => {
152
+ const runSource = fs.readFileSync('./src/cli/run.js', 'utf8');
153
+ expect(runSource).to.include('MongoExpress.deploy(');
154
+ expect(runSource).to.not.include('manifests/deployment/mongo-express/deployment.yaml');
155
+ });
156
+ });
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ import { expect } from 'chai';
4
+ import {
5
+ buildCrudController,
6
+ serviceHandler,
7
+ setCrossOriginHeaders,
8
+ } from '../../../../src/server/network/middlewares.js';
9
+
10
+ // Minimal Express doubles: the middleware only sets headers and sends one JSON envelope.
11
+ const requestFrom = (origin) => ({ query: {}, params: {}, path: '/', headers: origin ? { origin } : {} });
12
+ const responseSpy = () => {
13
+ const headers = {};
14
+ const response = {
15
+ headers,
16
+ body: undefined,
17
+ statusCode: 0,
18
+ set: (key, value) => (headers[key] = value),
19
+ setHeader: (key, value) => (headers[key] = value),
20
+ status(code) {
21
+ this.statusCode = code;
22
+ return this;
23
+ },
24
+ json(body) {
25
+ this.body = body;
26
+ return this;
27
+ },
28
+ };
29
+ return response;
30
+ };
31
+
32
+ const crudService = {
33
+ get: async () => ({ data: [], total: 0 }),
34
+ post: async () => ({}),
35
+ put: async () => ({}),
36
+ delete: async () => ({}),
37
+ };
38
+
39
+ describe('public API cross-origin policy', () => {
40
+ it('answers a browser read with the origin it came from', async () => {
41
+ // The browser client is served from its own origin: a read without this header arrives with
42
+ // status 200 and is then discarded by the same-origin policy, which looks like a broken
43
+ // feature rather than a failed request.
44
+ const response = responseSpy();
45
+ await buildCrudController(crudService).get(requestFrom('http://localhost:8082'), response, {});
46
+ expect(response.headers['Access-Control-Allow-Origin']).to.equal('http://localhost:8082');
47
+ expect(response.headers['Cross-Origin-Resource-Policy']).to.equal('cross-origin');
48
+ expect(response.statusCode).to.equal(200);
49
+ expect(response.body.status).to.equal('success');
50
+ });
51
+
52
+ it('falls back to any origin when the request declares none', async () => {
53
+ const response = responseSpy();
54
+ await buildCrudController(crudService).get(requestFrom(), response, {});
55
+ expect(response.headers['Access-Control-Allow-Origin']).to.equal('*');
56
+ });
57
+
58
+ it('keeps writes same-origin: only reads are public', async () => {
59
+ const response = responseSpy();
60
+ await buildCrudController(crudService).post(requestFrom('http://localhost:8082'), response, {});
61
+ expect(response.headers).to.not.have.property('Access-Control-Allow-Origin');
62
+ });
63
+
64
+ it('carries the same policy on a hand-written read handler', async () => {
65
+ // `setCrossOriginHeaders` is the one place the policy lives, so a custom route opts in with it
66
+ // rather than restating the headers.
67
+ const response = responseSpy();
68
+ await serviceHandler(async () => ({ ok: true }), { crossOrigin: true })(
69
+ requestFrom('https://www.cyberiaonline.com'),
70
+ response,
71
+ {},
72
+ );
73
+ expect(response.headers['Access-Control-Allow-Origin']).to.equal('https://www.cyberiaonline.com');
74
+
75
+ const direct = responseSpy();
76
+ setCrossOriginHeaders(requestFrom('https://www.cyberiaonline.com'), direct);
77
+ expect(direct.headers).to.deep.equal(response.headers);
78
+ });
79
+ });