cyberia 3.2.9 → 3.2.22

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 (184) hide show
  1. package/.github/workflows/engine-cyberia.cd.yml +7 -0
  2. package/.github/workflows/engine-cyberia.ci.yml +14 -2
  3. package/.github/workflows/ghpkg.ci.yml +1 -0
  4. package/.github/workflows/npmpkg.ci.yml +10 -5
  5. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  6. package/.github/workflows/release.cd.yml +1 -0
  7. package/.vscode/extensions.json +9 -9
  8. package/.vscode/settings.json +20 -4
  9. package/CHANGELOG.md +363 -1
  10. package/CLI-HELP.md +975 -1061
  11. package/README.md +190 -348
  12. package/bin/build.js +102 -125
  13. package/bin/build.template.js +33 -0
  14. package/bin/cyberia.js +238 -56
  15. package/bin/deploy.js +16 -3
  16. package/bin/index.js +238 -56
  17. package/bump.config.js +26 -0
  18. package/conf.js +131 -24
  19. package/deployment.yaml +76 -2
  20. package/hardhat/package-lock.json +113 -144
  21. package/hardhat/package.json +4 -3
  22. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  23. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  24. package/manifests/deployment/dd-cyberia-development/deployment.yaml +76 -2
  25. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  26. package/manifests/kind-config-dev.yaml +8 -0
  27. package/manifests/lxd/lxd-admin-profile.yaml +12 -3
  28. package/manifests/mongodb/pv-pvc.yaml +44 -8
  29. package/manifests/mongodb/statefulset.yaml +55 -68
  30. package/manifests/mongodb-4.4/headless-service.yaml +10 -0
  31. package/manifests/mongodb-4.4/kustomization.yaml +3 -1
  32. package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
  33. package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
  34. package/manifests/mongodb-4.4/statefulset.yaml +79 -0
  35. package/manifests/mongodb-4.4/storage-class.yaml +9 -0
  36. package/manifests/valkey/statefulset.yaml +1 -1
  37. package/manifests/valkey/valkey-nodeport.yaml +17 -0
  38. package/package.json +31 -19
  39. package/scripts/ipxe-setup.sh +52 -49
  40. package/scripts/k3s-node-setup.sh +81 -46
  41. package/scripts/link-local-underpost-cli.sh +6 -0
  42. package/scripts/lxd-vm-setup.sh +193 -8
  43. package/scripts/maas-nat-firewalld.sh +145 -0
  44. package/scripts/test-monitor.sh +250 -0
  45. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +38 -33
  46. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +16 -16
  47. package/src/api/core/core.router.js +19 -14
  48. package/src/api/core/core.service.js +5 -5
  49. package/src/api/crypto/crypto.router.js +18 -12
  50. package/src/api/crypto/crypto.service.js +3 -3
  51. package/src/api/cyberia-action/cyberia-action.model.js +1 -1
  52. package/src/api/cyberia-action/cyberia-action.router.js +22 -18
  53. package/src/api/cyberia-action/cyberia-action.service.js +5 -5
  54. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +74 -0
  55. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +99 -0
  56. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +98 -0
  57. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
  58. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +25 -20
  59. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +6 -6
  60. package/src/api/cyberia-entity/cyberia-entity.router.js +22 -18
  61. package/src/api/cyberia-entity/cyberia-entity.service.js +5 -5
  62. package/src/api/cyberia-instance/cyberia-fallback-world.js +79 -4
  63. package/src/api/cyberia-instance/cyberia-instance.router.js +57 -52
  64. package/src/api/cyberia-instance/cyberia-instance.service.js +10 -10
  65. package/src/api/cyberia-instance/cyberia-world-generator.js +3 -3
  66. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +14 -48
  67. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +22 -18
  68. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +5 -5
  69. package/src/api/cyberia-map/cyberia-map.router.js +35 -30
  70. package/src/api/cyberia-map/cyberia-map.service.js +7 -7
  71. package/src/api/cyberia-quest/cyberia-quest.model.js +1 -1
  72. package/src/api/cyberia-quest/cyberia-quest.router.js +22 -18
  73. package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
  74. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +22 -18
  75. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +5 -5
  76. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +458 -0
  77. package/src/api/default/default.router.js +22 -18
  78. package/src/api/default/default.service.js +5 -5
  79. package/src/api/document/document.router.js +28 -23
  80. package/src/api/document/document.service.js +100 -23
  81. package/src/api/file/file.router.js +19 -13
  82. package/src/api/file/file.service.js +9 -7
  83. package/src/api/instance/instance.router.js +29 -24
  84. package/src/api/instance/instance.service.js +6 -6
  85. package/src/api/ipfs/ipfs.router.js +21 -16
  86. package/src/api/ipfs/ipfs.service.js +8 -8
  87. package/src/api/object-layer/object-layer.router.js +512 -507
  88. package/src/api/object-layer/object-layer.service.js +17 -14
  89. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +22 -18
  90. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +5 -5
  91. package/src/api/test/test.router.js +17 -12
  92. package/src/api/types.js +24 -0
  93. package/src/api/user/guest.service.js +5 -4
  94. package/src/api/user/user.router.js +297 -288
  95. package/src/api/user/user.service.js +100 -35
  96. package/src/cli/baremetal.js +132 -101
  97. package/src/cli/cluster.js +700 -232
  98. package/src/cli/db.js +59 -60
  99. package/src/cli/deploy.js +291 -294
  100. package/src/cli/env.js +1 -4
  101. package/src/cli/fs.js +13 -3
  102. package/src/cli/image.js +58 -4
  103. package/src/cli/index.js +127 -15
  104. package/src/cli/ipfs.js +4 -6
  105. package/src/cli/kubectl.js +4 -1
  106. package/src/cli/lxd.js +1099 -223
  107. package/src/cli/monitor.js +396 -9
  108. package/src/cli/release.js +355 -146
  109. package/src/cli/repository.js +169 -30
  110. package/src/cli/run.js +347 -117
  111. package/src/cli/secrets.js +11 -2
  112. package/src/cli/test.js +9 -3
  113. package/src/client/Default.index.js +9 -3
  114. package/src/client/components/core/Auth.js +5 -0
  115. package/src/client/components/core/ClientEvents.js +76 -0
  116. package/src/client/components/core/EventBus.js +4 -0
  117. package/src/client/components/core/Modal.js +82 -41
  118. package/src/client/components/core/PanelForm.js +14 -10
  119. package/src/client/components/core/Worker.js +162 -363
  120. package/src/client/components/cyberia/MapEngineCyberia.js +1 -1
  121. package/src/client/components/cyberia/SharedDefaultsCyberia.js +330 -0
  122. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +55 -1
  123. package/src/client/public/cyberia-docs/ARCHITECTURE.md +223 -361
  124. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +114 -327
  125. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +200 -222
  126. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +212 -185
  127. package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
  128. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +2 -2
  129. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +23 -1
  130. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  131. package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
  132. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  133. package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
  134. package/src/client/ssr/views/CyberiaServerMetrics.js +982 -0
  135. package/src/client/sw/core.sw.js +174 -112
  136. package/src/db/DataBaseProvider.js +115 -15
  137. package/src/db/mariadb/MariaDB.js +2 -1
  138. package/src/db/mongo/MongoBootstrap.js +657 -0
  139. package/src/db/mongo/MongooseDB.js +130 -21
  140. package/src/grpc/cyberia/grpc-server.js +25 -57
  141. package/src/index.js +1 -1
  142. package/src/runtime/cyberia-client/Dockerfile +10 -7
  143. package/src/runtime/cyberia-client/Dockerfile.dev +67 -0
  144. package/src/runtime/cyberia-server/Dockerfile +11 -6
  145. package/src/runtime/cyberia-server/Dockerfile.dev +47 -0
  146. package/src/runtime/express/Express.js +2 -2
  147. package/src/runtime/wp/Dockerfile +3 -3
  148. package/src/runtime/wp/Wp.js +8 -5
  149. package/src/server/auth.js +2 -2
  150. package/src/server/catalog-underpost.js +61 -0
  151. package/src/server/catalog.js +77 -0
  152. package/src/server/client-build-docs.js +1 -1
  153. package/src/server/client-build.js +94 -129
  154. package/src/server/conf.js +496 -135
  155. package/src/server/ipfs-client.js +5 -3
  156. package/src/server/process.js +180 -19
  157. package/src/server/proxy.js +9 -2
  158. package/src/server/runtime-status.js +235 -0
  159. package/src/server/runtime.js +1 -1
  160. package/src/server/start.js +44 -11
  161. package/src/server/valkey.js +2 -0
  162. package/src/ws/IoInterface.js +16 -16
  163. package/src/ws/core/channels/core.ws.chat.js +11 -11
  164. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  165. package/src/ws/core/channels/core.ws.stream.js +19 -19
  166. package/src/ws/core/core.ws.connection.js +8 -8
  167. package/src/ws/core/core.ws.server.js +6 -5
  168. package/src/ws/default/channels/default.ws.main.js +10 -10
  169. package/src/ws/default/default.ws.connection.js +4 -4
  170. package/src/ws/default/default.ws.server.js +4 -3
  171. package/test/deploy-monitor.test.js +251 -0
  172. package/bin/file.js +0 -202
  173. package/bin/vs.js +0 -74
  174. package/bin/zed.js +0 -84
  175. package/manifests/deployment/dd-test-development/deployment.yaml +0 -254
  176. package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
  177. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +0 -574
  178. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +0 -467
  179. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  180. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  181. package/src/client/ssr/pages/CyberiaServerMetrics.js +0 -461
  182. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  183. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  184. /package/src/client/ssr/{pages → views}/Test.js +0 -0
package/bin/index.js CHANGED
@@ -20,7 +20,7 @@ import stringify from 'fast-json-stable-stringify';
20
20
  import { shellExec } from '../src/server/process.js';
21
21
  import { loggerFactory } from '../src/server/logger.js';
22
22
  import { generateBesuManifests, deployBesu, removeBesu } from '../src/server/besu-genesis-generator.js';
23
- import { DataBaseProvider } from '../src/db/DataBaseProvider.js';
23
+ import { DataBaseProviderService } from '../src/db/DataBaseProvider.js';
24
24
  import { loadConfServerJson } from '../src/server/conf.js';
25
25
  import {
26
26
  ObjectLayerEngine,
@@ -45,7 +45,7 @@ import {
45
45
  DefaultCyberiaDialogues,
46
46
  DefaultCyberiaActions,
47
47
  DefaultCyberiaQuests,
48
- } from '../src/client/components/cyberia-portal/CommonCyberiaPortal.js';
48
+ } from '../src/api/cyberia-server-defaults/cyberia-server-defaults.js';
49
49
 
50
50
  /**
51
51
  * Connect to the project MongoDB instance using the standard env / conf layout.
@@ -72,14 +72,14 @@ async function connectDbForChain({ envPath, mongoHost }) {
72
72
 
73
73
  db.host = mongoHost ? mongoHost : db.host.replace('127.0.0.1', 'mongodb-0.mongodb-service');
74
74
 
75
- await DataBaseProvider.load({
75
+ await DataBaseProviderService.load({
76
76
  apis: ['object-layer'],
77
77
  host,
78
78
  path,
79
79
  db,
80
80
  });
81
81
 
82
- const ObjectLayer = DataBaseProvider.instance[`${host}${path}`].mongoose.models.ObjectLayer;
82
+ const ObjectLayer = DataBaseProviderService.getModel('object-layer', { host, path });
83
83
  return { ObjectLayer, host, path };
84
84
  }
85
85
 
@@ -213,7 +213,7 @@ try {
213
213
  db,
214
214
  });
215
215
 
216
- await DataBaseProvider.load({
216
+ await DataBaseProviderService.load({
217
217
  apis: ['object-layer', 'object-layer-render-frames', 'atlas-sprite-sheet', 'file', 'ipfs'],
218
218
  host,
219
219
  path,
@@ -221,16 +221,15 @@ try {
221
221
  });
222
222
 
223
223
  /** @type {import('mongoose').Model} */
224
- const ObjectLayer = DataBaseProvider.instance[`${host}${path}`].mongoose.models.ObjectLayer;
224
+ const ObjectLayer = DataBaseProviderService.getModel('object-layer', { host, path });
225
225
  /** @type {import('mongoose').Model} */
226
- const ObjectLayerRenderFrames =
227
- DataBaseProvider.instance[`${host}${path}`].mongoose.models.ObjectLayerRenderFrames;
226
+ const ObjectLayerRenderFrames = DataBaseProviderService.getModel('object-layer-render-frames', { host, path });
228
227
  /** @type {import('mongoose').Model} */
229
- const AtlasSpriteSheet = DataBaseProvider.instance[`${host}${path}`].mongoose.models.AtlasSpriteSheet;
228
+ const AtlasSpriteSheet = DataBaseProviderService.getModel('atlas-sprite-sheet', { host, path });
230
229
  /** @type {import('mongoose').Model} */
231
- const File = DataBaseProvider.instance[`${host}${path}`].mongoose.models.File;
230
+ const File = DataBaseProviderService.getModel('file', { host, path });
232
231
  /** @type {import('mongoose').Model} */
233
- const Ipfs = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Ipfs;
232
+ const Ipfs = DataBaseProviderService.getModel('ipfs', { host, path });
234
233
 
235
234
  if (options.drop) {
236
235
  // Parse comma-separated item IDs for targeted drop; if none provided, drop everything
@@ -1694,7 +1693,7 @@ try {
1694
1693
  }
1695
1694
  }
1696
1695
 
1697
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
1696
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
1698
1697
  },
1699
1698
  )
1700
1699
  .description('Object layer management');
@@ -1749,7 +1748,7 @@ try {
1749
1748
 
1750
1749
  logger.info('instance env', { env: options.envPath, deployId, host, path, db });
1751
1750
 
1752
- await DataBaseProvider.load({
1751
+ await DataBaseProviderService.load({
1753
1752
  apis: [
1754
1753
  'cyberia-instance',
1755
1754
  'cyberia-instance-conf',
@@ -1767,16 +1766,15 @@ try {
1767
1766
  db,
1768
1767
  });
1769
1768
 
1770
- const dbModels = DataBaseProvider.instance[`${host}${path}`].mongoose.models;
1771
- const CyberiaInstance = dbModels.CyberiaInstance;
1772
- const CyberiaInstanceConf = dbModels.CyberiaInstanceConf;
1773
- const CyberiaDialogue = dbModels.CyberiaDialogue;
1774
- const CyberiaMap = dbModels.CyberiaMap;
1775
- const ObjectLayer = dbModels.ObjectLayer;
1776
- const ObjectLayerRenderFrames = dbModels.ObjectLayerRenderFrames;
1777
- const AtlasSpriteSheet = dbModels.AtlasSpriteSheet;
1778
- const File = dbModels.File;
1779
- const Ipfs = dbModels.Ipfs;
1769
+ const CyberiaInstance = DataBaseProviderService.getModel('cyberia-instance', { host, path });
1770
+ const CyberiaInstanceConf = DataBaseProviderService.getModel('cyberia-instance-conf', { host, path });
1771
+ const CyberiaDialogue = DataBaseProviderService.getModel('cyberia-dialogue', { host, path });
1772
+ const CyberiaMap = DataBaseProviderService.getModel('cyberia-map', { host, path });
1773
+ const ObjectLayer = DataBaseProviderService.getModel('object-layer', { host, path });
1774
+ const ObjectLayerRenderFrames = DataBaseProviderService.getModel('object-layer-render-frames', { host, path });
1775
+ const AtlasSpriteSheet = DataBaseProviderService.getModel('atlas-sprite-sheet', { host, path });
1776
+ const File = DataBaseProviderService.getModel('file', { host, path });
1777
+ const Ipfs = DataBaseProviderService.getModel('ipfs', { host, path });
1780
1778
 
1781
1779
  const toBuffer = (value) => {
1782
1780
  if (!value) return null;
@@ -1911,7 +1909,7 @@ try {
1911
1909
  const instance = await CyberiaInstance.findOne({ code: instanceCode }).lean();
1912
1910
  if (!instance) {
1913
1911
  logger.error(`CyberiaInstance with code "${instanceCode}" not found`);
1914
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
1912
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
1915
1913
  process.exit(1);
1916
1914
  }
1917
1915
 
@@ -1978,7 +1976,7 @@ try {
1978
1976
  backupDir,
1979
1977
  exportedFiles: ['cyberia-instance.json', 'cyberia-instance-conf.json'],
1980
1978
  });
1981
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
1979
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
1982
1980
  return;
1983
1981
  }
1984
1982
 
@@ -2293,7 +2291,7 @@ try {
2293
2291
  for (const failure of ipfsPayloadFailures) {
2294
2292
  logger.error('Canonical IPFS payload export failed', failure);
2295
2293
  }
2296
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
2294
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
2297
2295
  process.exit(1);
2298
2296
  }
2299
2297
 
@@ -2372,7 +2370,7 @@ try {
2372
2370
 
2373
2371
  if (!fs.existsSync(backupDir)) {
2374
2372
  logger.error(`Backup directory not found: ${backupDir}`);
2375
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
2373
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
2376
2374
  process.exit(1);
2377
2375
  }
2378
2376
 
@@ -2594,7 +2592,7 @@ try {
2594
2592
  backupDir,
2595
2593
  importedFiles: ['cyberia-instance.json', 'cyberia-instance-conf.json'],
2596
2594
  });
2597
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
2595
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
2598
2596
  return;
2599
2597
  }
2600
2598
 
@@ -3211,7 +3209,106 @@ try {
3211
3209
  logger.error('Specify --export, --import, or --drop flag');
3212
3210
  }
3213
3211
 
3214
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
3212
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
3213
+ });
3214
+
3215
+ // ── client-hints: presentation hints management ──────────────────────────
3216
+ program
3217
+ .command('client-hints [instance-code]')
3218
+ .option('--export [path]', 'Export CyberiaClientHints document to JSON (default: ./client-hints-<code>.json)')
3219
+ .option('--import [path]', 'Upsert CyberiaClientHints from a JSON file')
3220
+ .option('--seed-defaults', 'Upsert canonical presentation-hint defaults for the given instance code')
3221
+ .option('--drop', 'Remove the CyberiaClientHints document for the given instance code')
3222
+ .option('--env-path <env-path>', 'Env path e.g. ./engine-private/conf/dd-cyberia/.env.development')
3223
+ .option('--mongo-host <mongo-host>', 'Mongo host override')
3224
+ .option('--dev', 'Force development environment')
3225
+ .description('Manage per-instance client presentation hints (palette, camera, status icons, interpolation)')
3226
+ .action(async (instanceCode, options = {}) => {
3227
+ try {
3228
+ const envPath =
3229
+ options.envPath || `./engine-private/conf/dd-cyberia/.env.${options.dev ? 'development' : 'production'}`;
3230
+ if (fs.existsSync(envPath)) dotenv.config({ path: envPath, override: true });
3231
+
3232
+ const { CYBERIA_CLIENT_HINTS_DEFAULTS, buildClientHints } =
3233
+ await import('../src/client/components/cyberia/SharedDefaultsCyberia.js');
3234
+
3235
+ const deployId = process.env.DEFAULT_DEPLOY_ID;
3236
+ const host = process.env.DEFAULT_DEPLOY_HOST;
3237
+ const path = process.env.DEFAULT_DEPLOY_PATH;
3238
+ const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
3239
+ if (!fs.existsSync(confServerPath)) throw new Error(`Config not found: ${confServerPath}`);
3240
+ const confServer = loadConfServerJson(confServerPath, { resolve: true });
3241
+ const { db } = confServer[host][path];
3242
+ db.host = options.mongoHost ? options.mongoHost : db.host.replace('127.0.0.1', 'mongodb-0.mongodb-service');
3243
+
3244
+ await DataBaseProviderService.load({ apis: ['cyberia-client-hints'], host, path, db });
3245
+ const CyberiaClientHints = DataBaseProviderService.getModel('cyberia-client-hints', { host, path });
3246
+
3247
+ if (!instanceCode && !options.seedDefaults) {
3248
+ logger.error('instance-code required for client-hints operations (omit only with --seed-defaults on all)');
3249
+ process.exit(1);
3250
+ }
3251
+
3252
+ if (options.drop) {
3253
+ if (!instanceCode) {
3254
+ logger.error('instance-code required for --drop');
3255
+ process.exit(1);
3256
+ }
3257
+ const result = await CyberiaClientHints.deleteOne({ code: instanceCode });
3258
+ logger.info(`client-hints --drop: removed ${result.deletedCount} document(s) for code="${instanceCode}"`);
3259
+ }
3260
+
3261
+ if (options.seedDefaults) {
3262
+ const codes = instanceCode ? [instanceCode] : [];
3263
+ if (codes.length === 0) {
3264
+ logger.error('instance-code required for --seed-defaults');
3265
+ process.exit(1);
3266
+ }
3267
+ for (const code of codes) {
3268
+ await CyberiaClientHints.findOneAndUpdate(
3269
+ { code },
3270
+ { $setOnInsert: { code, ...CYBERIA_CLIENT_HINTS_DEFAULTS } },
3271
+ { upsert: true, returnDocument: 'after' },
3272
+ );
3273
+ logger.info(`client-hints --seed-defaults: seeded defaults for code="${code}"`);
3274
+ }
3275
+ }
3276
+
3277
+ if (options.import) {
3278
+ const filePath = typeof options.import === 'string' ? options.import : `./client-hints-${instanceCode}.json`;
3279
+ if (!fs.existsSync(filePath)) throw new Error(`Import file not found: ${filePath}`);
3280
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
3281
+ const code = data.code || instanceCode;
3282
+ if (!code) {
3283
+ logger.error('instance-code required (from file.code or CLI argument)');
3284
+ process.exit(1);
3285
+ }
3286
+ await CyberiaClientHints.findOneAndUpdate({ code }, { $set: { code, ...data } }, { upsert: true, new: true });
3287
+ logger.info(`client-hints --import: upserted code="${code}" from ${filePath}`);
3288
+ }
3289
+
3290
+ if (options.export) {
3291
+ if (!instanceCode) {
3292
+ logger.error('instance-code required for --export');
3293
+ process.exit(1);
3294
+ }
3295
+ const doc = await CyberiaClientHints.findOne({ code: instanceCode }).lean();
3296
+ if (!doc) {
3297
+ logger.warn(`No client-hints document found for code="${instanceCode}", exporting defaults`);
3298
+ }
3299
+ const outPath = typeof options.export === 'string' ? options.export : `./client-hints-${instanceCode}.json`;
3300
+ fs.writeFileSync(
3301
+ outPath,
3302
+ JSON.stringify(doc || { code: instanceCode, ...CYBERIA_CLIENT_HINTS_DEFAULTS }, null, 2),
3303
+ );
3304
+ logger.info(`client-hints --export: wrote ${outPath}`);
3305
+ }
3306
+
3307
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
3308
+ } catch (err) {
3309
+ logger.error('client-hints command error:', err);
3310
+ process.exit(1);
3311
+ }
3215
3312
  });
3216
3313
 
3217
3314
  // ── chain: Hyperledger Besu / ERC-1155 lifecycle commands ────────────────
@@ -3411,7 +3508,7 @@ try {
3411
3508
  logger.info(` SHA-256: ${resolved.sha256}`);
3412
3509
 
3413
3510
  // Close the DB connection after resolving
3414
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
3511
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
3415
3512
  } catch (dbErr) {
3416
3513
  logger.error(`Failed to resolve canonical CID from database: ${dbErr.message}`);
3417
3514
  process.exit(1);
@@ -3979,7 +4076,7 @@ try {
3979
4076
  }
3980
4077
 
3981
4078
  try {
3982
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
4079
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
3983
4080
  } catch (_) {
3984
4081
  /* ignore close errors */
3985
4082
  }
@@ -4034,12 +4131,33 @@ try {
4034
4131
  runner
4035
4132
  .command('import-default-items')
4036
4133
  .option('--dev', 'Force development environment (loads .env.development for IPFS localhost, etc.)')
4037
- .description('Import default Object Layer items, skill config, and dialogues into MongoDB')
4134
+ .option(
4135
+ '--mongo-host <mongo-host>',
4136
+ 'Mongo host override (forwarded to ol, seed-skill-config, seed-dialogues, client-hints)',
4137
+ )
4138
+ .description('Import default Object Layer items, skill config, dialogues, and client-hints into MongoDB')
4038
4139
  .action(async (options) => {
4140
+ // Pre-flight: every item id referenced by the fallback world must
4141
+ // exist in DefaultCyberiaItems. Drift here causes silent missing
4142
+ // sprites at runtime, so fail loudly before we touch MongoDB.
4143
+ const { auditFallbackItemIds } = await import('../src/api/cyberia-instance/cyberia-fallback-world.js');
4144
+ const missing = auditFallbackItemIds();
4145
+ if (missing.length > 0) {
4146
+ logger.error(
4147
+ 'import-default-items aborted: item ids referenced by defaults are missing from DefaultCyberiaItems:',
4148
+ missing.join(', '),
4149
+ '— add them to cyberia-server-defaults.js before seeding.',
4150
+ );
4151
+ process.exit(1);
4152
+ }
4153
+
4039
4154
  const devFlag = options.dev ? ' --dev' : '';
4040
- shellExec(`node bin/cyberia ol ${DefaultCyberiaItems.map((e) => e.item.id)} --import${devFlag}`);
4041
- shellExec(`node bin/cyberia run-workflow seed-skill-config${devFlag}`);
4042
- shellExec(`node bin/cyberia run-workflow seed-dialogues${devFlag}`);
4155
+ const mongoHostFlag = options.mongoHost ? ` --mongo-host ${options.mongoHost}` : '';
4156
+ const instanceCode = process.env.INSTANCE_CODE || 'cyberia-main';
4157
+ shellExec(`node bin/cyberia ol ${DefaultCyberiaItems.map((e) => e.item.id)} --import${devFlag}${mongoHostFlag}`);
4158
+ shellExec(`node bin/cyberia run-workflow seed-skill-config${devFlag}${mongoHostFlag}`);
4159
+ shellExec(`node bin/cyberia run-workflow seed-dialogues${devFlag}${mongoHostFlag}`);
4160
+ shellExec(`node bin/cyberia client-hints ${instanceCode} --seed-defaults${devFlag}${mongoHostFlag}`);
4043
4161
  });
4044
4162
 
4045
4163
  runner
@@ -4079,10 +4197,10 @@ try {
4079
4197
 
4080
4198
  logger.info('seed-skill-config', { instanceCode, deployId, host, path, db });
4081
4199
 
4082
- await DataBaseProvider.load({ apis: ['cyberia-instance', 'cyberia-instance-conf'], host, path, db });
4200
+ await DataBaseProviderService.load({ apis: ['cyberia-instance', 'cyberia-instance-conf'], host, path, db });
4083
4201
 
4084
- const CyberiaInstance = DataBaseProvider.instance[`${host}${path}`].mongoose.models.CyberiaInstance;
4085
- const CyberiaInstanceConf = DataBaseProvider.instance[`${host}${path}`].mongoose.models.CyberiaInstanceConf;
4202
+ const CyberiaInstance = DataBaseProviderService.getModel('cyberia-instance', { host, path });
4203
+ const CyberiaInstanceConf = DataBaseProviderService.getModel('cyberia-instance-conf', { host, path });
4086
4204
 
4087
4205
  const instance = await CyberiaInstance.findOne({ code: instanceCode }).lean();
4088
4206
 
@@ -4110,7 +4228,7 @@ try {
4110
4228
  DefaultSkillConfig.map((e) => `${e.triggerItemId} → [${e.logicEventIds.join(', ')}]`),
4111
4229
  );
4112
4230
 
4113
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
4231
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
4114
4232
  });
4115
4233
 
4116
4234
  runner
@@ -4148,9 +4266,9 @@ try {
4148
4266
 
4149
4267
  logger.info('seed-dialogues', { deployId, host, path, db });
4150
4268
 
4151
- await DataBaseProvider.load({ apis: ['cyberia-dialogue'], host, path, db });
4269
+ await DataBaseProviderService.load({ apis: ['cyberia-dialogue'], host, path, db });
4152
4270
 
4153
- const CyberiaDialogue = DataBaseProvider.instance[`${host}${path}`].mongoose.models.CyberiaDialogue;
4271
+ const CyberiaDialogue = DataBaseProviderService.getModel('cyberia-dialogue', { host, path });
4154
4272
 
4155
4273
  // Upsert each dialogue record keyed by (code, order) — idempotent.
4156
4274
  let upserted = 0;
@@ -4165,7 +4283,7 @@ try {
4165
4283
 
4166
4284
  logger.info(`seed-dialogues: ${upserted} dialogue records upserted`);
4167
4285
 
4168
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
4286
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
4169
4287
  });
4170
4288
 
4171
4289
  runner
@@ -4231,25 +4349,89 @@ try {
4231
4349
 
4232
4350
  runner
4233
4351
  .command('build-manifest')
4234
- .description('Build k8s resource manifest YAML files from templates')
4235
- .action(() => {
4236
- shellExec(`node bin run instance-build-manifest 'dd-cyberia,mmo-client,./cyberia-client' --kubeadm`);
4237
- shellExec(`node bin run instance-build-manifest 'dd-cyberia,mmo-client,./cyberia-client' --kind --dev`);
4238
- shellExec(`node bin run instance-build-manifest 'dd-cyberia,mmo-server,./cyberia-server' --kubeadm`);
4239
- shellExec(`node bin run instance-build-manifest 'dd-cyberia,mmo-server,./cyberia-server' --kind --dev`);
4352
+ .option(
4353
+ '--dev',
4354
+ 'Build dev-variant manifests (kind cluster, Dockerfile.dev). Default builds prod (kubeadm, Dockerfile).',
4355
+ )
4356
+ .description(
4357
+ 'Build k8s resource manifests for the Cyberia mmo-server + mmo-client instances. ' +
4358
+ 'Without --dev: production manifests (Dockerfile, kubeadm). With --dev: dev manifests (Dockerfile.dev, kind).',
4359
+ )
4360
+ .action((options) => {
4361
+ const isDev = !!options.dev;
4362
+ const flags = isDev ? '--kind --dev' : '--kubeadm';
4363
+ // shellExec is fail-fast by default: any non-zero exit throws
4364
+ // ShellExecError, which propagates to the outer catch and exits
4365
+ // the CLI non-zero — observable by GitHub Actions.
4366
+ shellExec(`node bin run instance-build-manifest 'dd-cyberia,mmo-client,./cyberia-client' ${flags}`);
4367
+ shellExec(`node bin run instance-build-manifest 'dd-cyberia,mmo-server,./cyberia-server' ${flags}`);
4368
+ // Copy canonical doc sources into the generated project READMEs.
4369
+ // Edit the canonical sources; never hand-edit these generated outputs.
4370
+ fs.copyFileSync('./src/client/public/cyberia-docs/CYBERIA-CLIENT.md', './cyberia-client/README.md');
4371
+ fs.copyFileSync('./src/client/public/cyberia-docs/CYBERIA-SERVER.md', './cyberia-server/README.md');
4372
+ logger.info(`run-workflow build-manifest complete (${isDev ? 'dev' : 'prod'})`);
4373
+ });
4374
+
4375
+ runner
4376
+ .command('build-server-dashboard')
4377
+ .option(
4378
+ '--dev',
4379
+ 'Build a development variant of the dashboard with dev-specific env vars (e.g. localhost API endpoints).',
4380
+ )
4381
+ .option(
4382
+ '--output-path <path>',
4383
+ 'Override output path for the rendered HTML (default: ./cyberia-server/public/index.html). ' +
4384
+ 'Used by CI when this command is invoked from inside an engine checkout that lives ' +
4385
+ 'alongside (not inside) the cyberia-server repo — pass e.g. ../public/index.html.',
4386
+ )
4387
+ .description('Build a static HTML dashboard for cyberia-server metrics and operational status. ')
4388
+ .action((options) => {
4389
+ const outputPath = options.outputPath || './cyberia-server/public/index.html';
4390
+ shellExec(
4391
+ `node bin static --page ./src/client/ssr/views/CyberiaServerMetrics.js` +
4392
+ ` --output-path ${outputPath}` +
4393
+ ` --title 'Cyberia Server Metrics'` +
4394
+ ` --favicon /favicon.ico` +
4395
+ ` --description 'Operational dashboard for the cyberia-server MMO runtime.'` +
4396
+ ` --lang en` +
4397
+ ` --env ${options.dev ? 'development' : 'production'}`,
4398
+ );
4240
4399
  });
4241
4400
 
4242
- if (underpostProgram.commands.find((c) => c._name == process.argv[2]))
4401
+ // Passthrough check: if the user invoked a command that is OWNED by the
4402
+ // underpost CLI (not the cyberia overlay), throw the sentinel error so
4403
+ // the catch block below can re-run argv through underpost. The match is
4404
+ // strict on process.argv[2] (the first positional after `node bin/cyberia`)
4405
+ // so we only passthrough when the top-level command name actually
4406
+ // belongs to underpost.
4407
+ if (
4408
+ process.argv[2] &&
4409
+ underpostProgram.commands.find((c) => c._name === process.argv[2]) &&
4410
+ !program.commands.find((c) => c._name === process.argv[2])
4411
+ ) {
4243
4412
  throw new Error('Trigger underpost passthrough');
4413
+ }
4244
4414
 
4245
- program.parse();
4415
+ await program.parseAsync();
4246
4416
  } catch (error) {
4247
- logger.warn(error);
4248
- process.argv = process.argv.filter((c) => c !== 'underpost');
4249
- logger.warn('Rerouting to underpost cli...');
4250
- try {
4251
- underpostProgram.parse();
4252
- } catch (error) {
4417
+ // ONLY reroute on the explicit passthrough sentinel. Any other thrown
4418
+ // error (subprocess non-zero from shellExec's fail-fast default, CLI
4419
+ // parse errors, missing modules) must propagate as a non-zero process
4420
+ // exit so GitHub Actions / CI parents observe the failure. Without this
4421
+ // guard, a genuine build failure was being silently rerouted into the
4422
+ // underpost CLI and then masked behind a misleading "unknown command"
4423
+ // line.
4424
+ if (error && error.message === 'Trigger underpost passthrough') {
4425
+ process.argv = process.argv.filter((c) => c !== 'underpost');
4426
+ logger.warn('Rerouting to underpost cli...');
4427
+ try {
4428
+ await underpostProgram.parseAsync();
4429
+ } catch (err) {
4430
+ logger.error(err);
4431
+ process.exit(1);
4432
+ }
4433
+ } else {
4253
4434
  logger.error(error);
4435
+ process.exit(1);
4254
4436
  }
4255
4437
  }
package/bump.config.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * bumpp configuration for the Underpost engine.
3
+ *
4
+ * Owns the *canonical* version-bearing files (anything that exposes a literal `version` field
5
+ * bumpp can detect natively). Non-canonical files — image tags in workflows, README badges,
6
+ * doc strings, deployment.yaml image refs — are handled by the custom regex walker in
7
+ * src/cli/release.js (VERSION_BUMP_TARGETS), because bumpp only rewrites `version`-shaped lines.
8
+ *
9
+ * release.js drives bumpp programmatically (versionBump from 'bumpp') with commit/tag/push
10
+ * disabled, since the engine release flow stages and commits separately via `node bin cmt`.
11
+ *
12
+ * @see https://github.com/antfu/bumpp
13
+ */
14
+ export default {
15
+ files: [
16
+ 'package.json',
17
+ 'package-lock.json',
18
+ // engine-private confs are git-ignored and visited only if present at bump time.
19
+ 'engine-private/conf/**/package.json',
20
+ ],
21
+ commit: false,
22
+ tag: false,
23
+ push: false,
24
+ confirm: false,
25
+ recursive: false,
26
+ };