@underpostnet/cyberia 3.3.73 → 3.3.77

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 (269) hide show
  1. package/.env.example +17 -5
  2. package/.github/workflows/coverall.cyberia.ci.yml +10 -0
  3. package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
  4. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
  5. package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
  6. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
  7. package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
  8. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
  9. package/.github/workflows/engine-cyberia.ci.yml +1 -1
  10. package/.github/workflows/publish.cyberia.ci.yml +44 -0
  11. package/AGENTS.md +61 -12
  12. package/CHANGELOG.md +159 -161
  13. package/CLI-HELP.md +20 -13
  14. package/Dockerfile +1 -1
  15. package/Dockerfile.dev +1 -1
  16. package/Dockerfile.test +2 -2
  17. package/README.md +28 -28
  18. package/bin/build.js +96 -21
  19. package/bin/cyberia.js +1247 -1373
  20. package/bin/index.js +1247 -1373
  21. package/compose.env +17 -5
  22. package/conf.js +7 -4
  23. package/deploy/cyberia-client/deploy.sh +10 -11
  24. package/deploy/cyberia-client/package.sh +0 -1
  25. package/deploy/cyberia-client/state.sh +3 -2
  26. package/deploy/cyberia-server/deploy.sh +10 -11
  27. package/deploy/cyberia-server/package.sh +0 -1
  28. package/deploy/cyberia-server/state.sh +3 -2
  29. package/deploy/dd-cyberia/deploy.sh +5 -5
  30. package/deploy/dd-cyberia/init.sh +15 -14
  31. package/deploy/dd-cyberia/package.sh +0 -1
  32. package/deploy/dd-cyberia/state.sh +2 -2
  33. package/deploy/dd-cyberia/sync-deploy.sh +139 -75
  34. package/deploy/lib/config.sh +29 -0
  35. package/deploy/lib/github-actions-logging.sh +92 -76
  36. package/deploy/lib/host.sh +28 -19
  37. package/deploy/lib/state.sh +4 -1
  38. package/deploy/pwa-microservices-template/deploy.sh +10 -8
  39. package/deploy/release/deploy.sh +0 -2
  40. package/deployment.yaml +1 -1
  41. package/docker-compose.yml +23 -17
  42. package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1591 -0
  43. package/docs/coverage/cyberia/api/cyberia-instance/index.html +116 -0
  44. package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +4138 -0
  45. package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +116 -0
  46. package/docs/coverage/cyberia/api/object-layer/index.html +116 -0
  47. package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1393 -0
  48. package/docs/coverage/cyberia/base.css +362 -0
  49. package/docs/coverage/cyberia/block-navigation.js +82 -0
  50. package/docs/coverage/cyberia/favicon.png +0 -0
  51. package/docs/coverage/cyberia/index.html +161 -0
  52. package/docs/coverage/cyberia/prettify.css +101 -0
  53. package/docs/coverage/cyberia/prettify.js +937 -0
  54. package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1315 -0
  55. package/docs/coverage/cyberia/projects/cyberia/index.html +176 -0
  56. package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +715 -0
  57. package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +2182 -0
  58. package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +3265 -0
  59. package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +328 -0
  60. package/docs/coverage/cyberia/sort-arrow-sprite.png +0 -0
  61. package/docs/coverage/cyberia/sorter.js +205 -0
  62. package/hardhat/package-lock.json +2 -2
  63. package/hardhat/package.json +1 -1
  64. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  65. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
  66. package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
  67. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  68. package/manifests/deployment/mongo-express/deployment.yaml +46 -27
  69. package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
  70. package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
  71. package/manifests/deployment/mongo-express/service.yaml +16 -0
  72. package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
  73. package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
  74. package/package.json +19 -17
  75. package/scripts/test-monitor.sh +3 -3
  76. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +14 -9
  77. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
  78. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  79. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +88 -184
  80. package/src/api/cyberia-action/cyberia-action.router.js +2 -2
  81. package/src/api/cyberia-action/cyberia-action.service.js +3 -4
  82. package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
  83. package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
  84. package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
  85. package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
  86. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -3
  87. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
  88. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +12 -34
  89. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +15 -32
  90. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
  91. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
  92. package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
  93. package/src/api/cyberia-entity/cyberia-entity.service.js +1 -1
  94. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
  95. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
  96. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +16 -1
  97. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +466 -3
  98. package/src/api/cyberia-instance/cyberia-fallback-capture.js +87 -51
  99. package/src/api/cyberia-instance/cyberia-fallback-world.js +21 -40
  100. package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
  101. package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
  102. package/src/api/cyberia-instance/cyberia-instance-map.service.js +34 -141
  103. package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -4
  104. package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
  105. package/src/api/cyberia-instance/cyberia-instance.router.js +3 -16
  106. package/src/api/cyberia-instance/cyberia-instance.service.js +3 -43
  107. package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
  108. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
  109. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +74 -1
  110. package/src/api/cyberia-map/cyberia-map.service.js +6 -2
  111. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
  112. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
  113. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
  114. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
  115. package/src/api/cyberia-quest/cyberia-quest.router.js +3 -3
  116. package/src/api/cyberia-quest/cyberia-quest.service.js +3 -3
  117. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
  118. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -188
  119. package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
  120. package/src/api/file/file.ref.js +153 -0
  121. package/src/api/file/file.ref.json +11 -2
  122. package/src/api/object-layer/object-layer.model.js +47 -12
  123. package/src/api/object-layer/object-layer.router.js +10 -3
  124. package/src/api/object-layer/object-layer.service.js +113 -120
  125. package/src/cli/app.js +12 -1
  126. package/src/cli/client.js +2 -2
  127. package/src/cli/cluster.js +80 -18
  128. package/src/cli/deploy.js +215 -34
  129. package/src/cli/docker-compose.js +1 -1
  130. package/src/cli/fs-selection.js +115 -0
  131. package/src/cli/fs.js +158 -342
  132. package/src/cli/index.js +22 -10
  133. package/src/cli/release.js +3 -1
  134. package/src/cli/repository.js +35 -9
  135. package/src/cli/run.js +80 -199
  136. package/src/cli/test.js +4 -0
  137. package/src/cli/wireguard.js +32 -31
  138. package/src/client/components/core/Badge.js +0 -1
  139. package/src/client/components/core/CssCore.js +8 -1
  140. package/src/client/components/core/Docs.js +77 -100
  141. package/src/client/components/core/Modal.js +77 -23
  142. package/src/client/components/core/ToolTip.js +15 -0
  143. package/src/client/components/cryptokoyn/AppShellCryptokoyn.js +0 -2
  144. package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
  145. package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
  146. package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
  147. package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
  148. package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
  149. package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
  150. package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
  151. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
  152. package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
  153. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +0 -34
  154. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
  155. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
  156. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +0 -4
  157. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
  158. package/src/client/components/default/AppShellDefault.js +1 -4
  159. package/src/client/components/itemledger/AppShellItemledger.js +0 -2
  160. package/src/client/components/underpost/AppShellUnderpost.js +1 -1
  161. package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
  162. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +280 -24
  163. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
  164. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
  165. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
  166. package/src/client/public/cyberia-docs/CYBERIA.md +28 -28
  167. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
  168. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  169. package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
  170. package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
  171. package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
  172. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
  173. package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
  174. package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
  175. package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
  176. package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
  177. package/src/client/services/object-layer/object-layer.management.js +6 -10
  178. package/src/client/services/object-layer/object-layer.service.js +3 -1
  179. package/src/client/ssr/views/Cyberia404.js +3 -3
  180. package/src/client-builder/client-build-docs.js +52 -52
  181. package/src/client-builder/client-build.js +67 -44
  182. package/src/client-builder/client-bundle.js +347 -0
  183. package/src/db/mongo/MongoExpress.js +187 -0
  184. package/src/grpc/cyberia/grpc-server.js +5 -7
  185. package/src/index.js +1 -1
  186. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +143 -146
  187. package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
  188. package/src/projects/cyberia/besu-genesis-generator.js +5 -14
  189. package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
  190. package/src/projects/cyberia/catalog-cyberia.js +3 -2
  191. package/src/projects/cyberia/gemini-client.js +3 -3
  192. package/src/projects/cyberia/generate-saga.js +4 -11
  193. package/src/projects/cyberia/hot-reload-trigger.js +17 -28
  194. package/src/projects/cyberia/instance-backup.js +210 -0
  195. package/src/projects/cyberia/instance-data.js +177 -257
  196. package/src/projects/cyberia/map-preview-generator.js +32 -68
  197. package/src/projects/cyberia/object-layer.js +152 -319
  198. package/src/projects/cyberia/seed-audio.js +139 -0
  199. package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
  200. package/src/projects/cyberia/semantic-layer-generator.js +2 -18
  201. package/src/projects/cyberia/stat-balance.js +81 -0
  202. package/src/projects/cyberia/stat-commands.js +23 -0
  203. package/src/projects/cyberia/stat-contract-generator.js +101 -0
  204. package/src/runtime/cyberia-client/Dockerfile +8 -4
  205. package/src/runtime/cyberia-client/Dockerfile.dev +5 -2
  206. package/src/runtime/cyberia-server/Dockerfile +9 -2
  207. package/src/runtime/cyberia-server/Dockerfile.dev +8 -1
  208. package/src/runtime/engine-cyberia/Dockerfile +1 -1
  209. package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
  210. package/src/runtime/engine-cyberia/Dockerfile.test +2 -2
  211. package/src/runtime/engine-cyberia/compose.env +17 -5
  212. package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
  213. package/src/server/build/coverage.js +113 -42
  214. package/src/server/build/package.js +55 -12
  215. package/src/server/build/testing.js +59 -9
  216. package/src/server/network/middlewares.js +8 -2
  217. package/src/server/network/underpost-gateway.js +113 -14
  218. package/src/server/ops/logger.js +70 -35
  219. package/src/server/runtime/conf.js +40 -9
  220. package/src/server/runtime/start.js +11 -2
  221. package/src/server/storage/data-query.js +16 -0
  222. package/src/server/storage/downloader.js +62 -14
  223. package/src/server/storage/zip.js +153 -0
  224. package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
  225. package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
  226. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +34 -0
  227. package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
  228. package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
  229. package/test/integration/app/cyberia/cyberia-instance-conf-defaults.test.js +16 -22
  230. package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
  231. package/test/integration/app/cyberia/cyberia-load.test.js +6 -7
  232. package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
  233. package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
  234. package/test/integration/app/cyberia/fallback-world-capture.test.js +87 -2
  235. package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
  236. package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
  237. package/test/integration/app/cyberia/seed-audio.test.js +275 -0
  238. package/test/integration/infra/2-network/wireguard-cli.test.js +33 -9
  239. package/test/integration/infra/2-network/wireguard-edge.test.js +58 -18
  240. package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
  241. package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
  242. package/test/integration/infra/4-ingress/underpost-ingress.test.js +1 -1
  243. package/test/unit/catalog.test.js +13 -2
  244. package/test/unit/client-build-docs.test.js +97 -70
  245. package/test/unit/client-bundle.test.js +313 -0
  246. package/test/unit/conf-resolution.test.js +58 -7
  247. package/test/unit/coverage-artifact.test.js +120 -29
  248. package/test/unit/cyberia/instance-backup.test.js +183 -0
  249. package/test/unit/cyberia/instance-data.test.js +314 -0
  250. package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
  251. package/test/unit/cyberia/publish-workflow.test.js +22 -0
  252. package/test/unit/cyberia/stat-balance.test.js +69 -0
  253. package/test/unit/deploy-legacy-gateway-sweep.test.js +63 -0
  254. package/test/unit/deploy-log-table.test.js +76 -5
  255. package/test/unit/downloader-integrity.test.js +98 -0
  256. package/test/unit/file-reference-registry.test.js +141 -0
  257. package/test/unit/fs-storage-paths.test.js +596 -0
  258. package/test/unit/gateway-pod-fetch.test.js +95 -0
  259. package/test/unit/letsencrypt-issuer.test.js +101 -0
  260. package/test/unit/logger-redaction.test.js +29 -0
  261. package/test/unit/package.test.js +70 -16
  262. package/test/unit/prepare-host.test.js +84 -1
  263. package/test/unit/release-bump.test.js +3 -1
  264. package/test/unit/start-options.test.js +12 -0
  265. package/test/unit/test-tiers.test.js +27 -0
  266. package/test/unit/zip-archive.test.js +142 -0
  267. package/vitest.config.js +11 -3
  268. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
  269. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
@@ -1413,9 +1413,9 @@ describe('engine sync', () => {
1413
1413
  const commands = UnderpostWireguard.API.syncCommands().map((step) => step.command);
1414
1414
  expect(commands.join('\n')).to.not.match(/<[a-z-]+>/);
1415
1415
  expect(commands.some((command) => command.includes('package.sh'))).to.equal(false);
1416
- expect(commands).to.include('underpost cmt --switch-repo someone/engine --target-branch master');
1416
+ expect(commands).to.include('node bin cmt --switch-repo someone/engine --target-branch master');
1417
1417
  expect(commands).to.include(
1418
- 'underpost cmt ./engine-private --switch-repo someone/engine-private --target-branch main',
1418
+ 'node bin cmt ./engine-private --switch-repo someone/engine-private --target-branch main',
1419
1419
  );
1420
1420
  });
1421
1421
 
@@ -1424,9 +1424,9 @@ describe('engine sync', () => {
1424
1424
  const commands = UnderpostWireguard.API.syncCommands({
1425
1425
  repoEngine: 'https://github.com/underpostnet/engine.git',
1426
1426
  }).map((step) => step.command);
1427
- expect(commands).to.include('underpost cmt --switch-repo underpostnet/engine --target-branch master');
1427
+ expect(commands).to.include('node bin cmt --switch-repo underpostnet/engine --target-branch master');
1428
1428
  expect(commands).to.include(
1429
- 'underpost cmt ./engine-private --switch-repo underpostnet/engine-private --target-branch main',
1429
+ 'node bin cmt ./engine-private --switch-repo underpostnet/engine-private --target-branch main',
1430
1430
  );
1431
1431
  });
1432
1432
 
@@ -1436,9 +1436,9 @@ describe('engine sync', () => {
1436
1436
  repoEngine: 'https://github.com/underpostnet/engine.git',
1437
1437
  repoEnginePrivate: 'https://github.com/underposnet/engine-core-private.git',
1438
1438
  }).map((step) => step.command);
1439
- expect(commands).to.include('underpost cmt --switch-repo underpostnet/engine --target-branch master');
1439
+ expect(commands).to.include('node bin cmt --switch-repo underpostnet/engine --target-branch master');
1440
1440
  expect(commands).to.include(
1441
- 'underpost cmt ./engine-private --switch-repo underposnet/engine-core-private --target-branch main',
1441
+ 'node bin cmt ./engine-private --switch-repo underposnet/engine-core-private --target-branch main',
1442
1442
  );
1443
1443
  });
1444
1444
 
@@ -1447,7 +1447,7 @@ describe('engine sync', () => {
1447
1447
  const commands = UnderpostWireguard.API.syncCommands({ cmd: 'uptime, hostname -I' }).map((step) => step.command);
1448
1448
  expect(commands).to.deep.equal(['uptime', 'hostname -I']);
1449
1449
  expect(commands.join('\n')).to.not.match(/<[a-z-]+>/);
1450
- expect(commands).to.not.include('underpost cmt --switch-repo');
1450
+ expect(commands).to.not.include('node bin cmt --switch-repo');
1451
1451
  });
1452
1452
 
1453
1453
  it('labels a --cmd fleet run as a command, not a sync', () => {
@@ -1462,7 +1462,7 @@ describe('engine sync', () => {
1462
1462
  process.env.GITHUB_USERNAME = 'someone';
1463
1463
  Underpost.repo.getDefaultBranch = () => 'trunk';
1464
1464
  expect(UnderpostWireguard.API.syncCommands().map((step) => step.command)).to.include(
1465
- 'underpost cmt --switch-repo someone/engine --target-branch trunk',
1465
+ 'node bin cmt --switch-repo someone/engine --target-branch trunk',
1466
1466
  );
1467
1467
  });
1468
1468
 
@@ -1542,10 +1542,10 @@ describe('engine sync', () => {
1542
1542
  const at = (command) => commands.indexOf(command);
1543
1543
 
1544
1544
  expect(at('node bin host set ENGINE_SRC_REPO underpostnet/engine-test-cyberia')).to.be.greaterThan(
1545
- at('underpost cmt --switch-repo underpostnet/engine-test-cyberia --target-branch master'),
1545
+ at('node bin cmt --switch-repo underpostnet/engine-test-cyberia --target-branch master'),
1546
1546
  );
1547
1547
  expect(at('node bin host set ENGINE_SRC_PRIVATE_REPO underpostnet/engine-cyberia-private')).to.be.greaterThan(
1548
- at('underpost cmt --switch-repo underpostnet/engine-test-cyberia --target-branch master'),
1548
+ at('node bin cmt --switch-repo underpostnet/engine-test-cyberia --target-branch master'),
1549
1549
  );
1550
1550
  });
1551
1551
 
@@ -1563,23 +1563,63 @@ describe('engine sync', () => {
1563
1563
  });
1564
1564
 
1565
1565
  it.skipIf(!shipsCyberiaPackageScript)(
1566
- 'runs a deploy package script only when the deploy being synced ships one',
1566
+ 'runs a deploy package script when the deploy ships one, and installs the checkout when it does not',
1567
1567
  () => {
1568
1568
  process.env.GITHUB_USERNAME = 'someone';
1569
1569
  const scripts = (repoEngine) =>
1570
- UnderpostWireguard.API.syncCommands({ repoEngine }).filter((step) => step.command.startsWith('bash '));
1570
+ UnderpostWireguard.API.syncCommands({ repoEngine })
1571
+ .filter((step) => step.command.startsWith('bash '))
1572
+ .map((step) => step.command);
1571
1573
 
1572
- // Both occurrences or neither: the halting one turns a missing script into a failed sync
1573
- // rather than a missing install.
1574
- expect(scripts('underpostnet/engine-test-cyberia').map((step) => step.command)).to.deep.equal([
1575
- 'bash ./deploy/dd-cyberia/package.sh',
1576
- 'bash ./deploy/dd-cyberia/package.sh',
1577
- ]);
1574
+ expect(scripts('underpostnet/engine-test-cyberia')).to.deep.equal(['bash ./deploy/dd-cyberia/package.sh']);
1578
1575
  expect(scripts('underpostnet/engine-test-absent')).to.deep.equal([]);
1579
1576
  expect(scripts('underpostnet/engine')).to.deep.equal([]);
1577
+
1578
+ // A sync that belongs to no deploy still lands new source, so the install is never dropped
1579
+ // — it falls back to the checkout's own manifest rather than leaving none.
1580
+ const commands = (repoEngine) => UnderpostWireguard.API.syncCommands({ repoEngine }).map((step) => step.command);
1581
+ expect(commands('underpostnet/engine')).to.include('npm install');
1582
+ expect(commands('underpostnet/engine-test-cyberia')).to.not.include('npm install');
1580
1583
  },
1581
1584
  );
1582
1585
 
1586
+ it('installs after the source switch with no CLI step in between', () => {
1587
+ process.env.GITHUB_USERNAME = 'someone';
1588
+ // Regression: the conf switch sat between the source switch and the install, so it ran the
1589
+ // freshly landed source against the previous `node_modules` and died importing a dependency
1590
+ // that switch had just added. Every step here is the node's own CLI, so none of them may
1591
+ // observe a checkout whose packages predate it.
1592
+ for (const repoEngine of ['underpostnet/engine', 'underpostnet/engine-test-cyberia']) {
1593
+ const commands = UnderpostWireguard.API.syncCommands({ repoEngine, nodeRole: 'control' }).map(
1594
+ (step) => step.command,
1595
+ );
1596
+ const switchAt = commands.findIndex(
1597
+ (command) => command.startsWith('node bin cmt --switch-repo') && !command.includes('./engine-private'),
1598
+ );
1599
+ const installAt = commands.findIndex(
1600
+ (command, index) => index > switchAt && (command.startsWith('bash ') || command === 'npm install'),
1601
+ );
1602
+
1603
+ expect(switchAt, repoEngine).to.be.greaterThan(-1);
1604
+ expect(installAt, repoEngine).to.equal(switchAt + 1);
1605
+ // The conf switch has to precede it, since the install reads the manifest that switch lands.
1606
+ expect(
1607
+ commands.findIndex((command) => command.includes('./engine-private --switch-repo')),
1608
+ repoEngine,
1609
+ ).to.be.lessThan(switchAt);
1610
+ }
1611
+ });
1612
+
1613
+ it('restores the checkout manifest before the bootstrap install', () => {
1614
+ process.env.GITHUB_USERNAME = 'someone';
1615
+ // A previous sync left the deploy manifest copied over ./package.json, so installing without
1616
+ // restoring it reinstalls the previous scope and leaves the CLI unable to load new source.
1617
+ const commands = UnderpostWireguard.API.syncCommands().map((step) => step.command);
1618
+ expect(commands[0]).to.equal('git checkout -- package.json; npm install');
1619
+ // Sequenced, not chained: a tree that is not a repository still has to reach the install.
1620
+ expect(commands[0]).to.not.include('&&');
1621
+ });
1622
+
1583
1623
  it('neutralizes advisory steps in place and chains the rest', () => {
1584
1624
  process.env.GITHUB_USERNAME = 'someone';
1585
1625
  const script = UnderpostWireguard.API.syncScript();
@@ -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
+ });
@@ -263,7 +263,7 @@ describe('shared ingress front', () => {
263
263
  );
264
264
  const testCandidate = install.indexOf("execIngress('nginx -t -c /tmp/nginx.candidate.conf'");
265
265
  const reload = install.indexOf('nginx -s reload -c /tmp/nginx.conf', testCandidate);
266
- const persist = install.indexOf("shellExec(`kubectl apply -f - -n ${namespace} <<'EOF'", reload);
266
+ const persist = install.indexOf("`kubectl apply -f - -n ${namespace} <<'EOF'", reload);
267
267
  expect(testCandidate).to.be.greaterThan(-1);
268
268
  expect(reload).to.be.greaterThan(testCandidate);
269
269
  expect(persist).to.be.greaterThan(reload);
@@ -14,6 +14,12 @@ import { TEST_TIERS } from '../../src/server/build/testing.js';
14
14
  const catalogs = await loadProductCatalogs();
15
15
  const describeProducts = describe.skipIf(catalogs.length === 0);
16
16
 
17
+ // A product template keeps its own catalog but has every other product sliced away, so only
18
+ // the unsliced engine tree carries every catalog and every tier directory. `buildTemplate`
19
+ // drops `build:template` from the manifest and every product manifest inherits that, which
20
+ // makes the script the one durable marker of the unsliced tree.
21
+ const unsliced = !!JSON.parse(fs.readFileSync('./package.json', 'utf8')).scripts?.['build:template'];
22
+
17
23
  describeProducts('product catalogs', () => {
18
24
  it('every catalog carries the uniform shape', () => {
19
25
  for (const catalog of catalogs)
@@ -54,14 +60,19 @@ describeProducts('product catalogs and the test tiers', () => {
54
60
  catalogs.some(({ stripPaths }) => stripPaths.includes(`./${directory}`)),
55
61
  );
56
62
 
57
- it('has products that own at least one tier', () => {
63
+ it.skipIf(!unsliced)('has products that own at least one tier', () => {
58
64
  expect(strippedTiers).to.not.be.empty;
59
65
  });
60
66
 
61
- it('gives every tier a directory that exists in an unsliced tree', () => {
67
+ it.skipIf(!unsliced)('gives every tier a directory that exists in an unsliced tree', () => {
62
68
  for (const { name, directory } of TEST_TIERS) expect(fs.existsSync(directory), name).to.equal(true);
63
69
  });
64
70
 
71
+ it('carries the directory of every tier a present product owns', () => {
72
+ // Holds in a product template too: what it kept of itself must be whole.
73
+ for (const { name, directory } of strippedTiers) expect(fs.existsSync(directory), name).to.equal(true);
74
+ });
75
+
65
76
  it('ships every tier it strips from the base template', () => {
66
77
  for (const { name, directory } of strippedTiers) {
67
78
  const owner = catalogs.find(({ stripPaths }) => stripPaths.includes(`./${directory}`));