cyberia 3.2.12 → 3.2.70

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 (194) hide show
  1. package/.env.example +127 -68
  2. package/.github/workflows/cyberia-client.cd.yml +40 -0
  3. package/.github/workflows/cyberia-server.cd.yml +40 -0
  4. package/.github/workflows/docker-image.cyberia-client.ci.yml +49 -0
  5. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +48 -0
  6. package/.github/workflows/docker-image.cyberia-server.ci.yml +69 -0
  7. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +69 -0
  8. package/.github/workflows/docker-image.engine-cyberia.ci.yml +52 -0
  9. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +52 -0
  10. package/.github/workflows/engine-cyberia.cd.yml +34 -24
  11. package/.github/workflows/engine-cyberia.ci.yml +27 -5
  12. package/.github/workflows/ghpkg.ci.yml +89 -1
  13. package/.github/workflows/gitlab.ci.yml +1 -1
  14. package/.github/workflows/hardhat.ci.yml +1 -1
  15. package/.github/workflows/npmpkg.ci.yml +19 -12
  16. package/.github/workflows/publish.ci.yml +2 -2
  17. package/.github/workflows/publish.cyberia.ci.yml +5 -16
  18. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -8
  19. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  20. package/CHANGELOG.md +451 -1
  21. package/CLI-HELP.md +1040 -1130
  22. package/Dockerfile +141 -43
  23. package/Dockerfile.dev +143 -0
  24. package/Dockerfile.test +165 -0
  25. package/README.md +1 -1
  26. package/baremetal/commission-workflows.json +1 -0
  27. package/bin/build.js +128 -136
  28. package/bin/build.template.js +25 -179
  29. package/bin/cyberia.js +1089 -188
  30. package/bin/deploy.js +6 -3
  31. package/bin/index.js +1089 -188
  32. package/bump.config.js +1 -0
  33. package/compose.env +131 -0
  34. package/conf.js +19 -1
  35. package/deployment.yaml +74 -2
  36. package/docker-compose.yml +316 -0
  37. package/hardhat/hardhat.config.js +2 -2
  38. package/hardhat/package-lock.json +620 -2041
  39. package/hardhat/package.json +7 -5
  40. package/hardhat/scripts/deployObjectLayerToken.js +18 -18
  41. package/hardhat/test/ObjectLayerToken.js +378 -274
  42. package/ipfs/configure-ipfs.sh +13 -0
  43. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  44. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  45. package/manifests/deployment/dd-cyberia-development/deployment.yaml +74 -2
  46. package/manifests/deployment/dd-cyberia-development/grpc-service.yaml +17 -0
  47. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +32 -0
  48. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  49. package/mongodb/entrypoint.sh +76 -0
  50. package/nginx.conf +87 -0
  51. package/package.json +34 -22
  52. package/pv-pvc.yaml +32 -0
  53. package/scripts/disk-clean.sh +85 -60
  54. package/scripts/kubeadm-node-setup.sh +317 -0
  55. package/scripts/link-local-underpost-cli.sh +6 -0
  56. package/scripts/rocky-kickstart.sh +877 -185
  57. package/scripts/rpmfusion-ffmpeg-setup.sh +26 -50
  58. package/scripts/test-monitor.sh +248 -0
  59. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +43 -7
  60. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +17 -25
  61. package/src/api/cyberia-action/cyberia-action.controller.js +19 -0
  62. package/src/api/cyberia-action/cyberia-action.model.js +21 -29
  63. package/src/api/cyberia-action/cyberia-action.router.js +42 -7
  64. package/src/api/cyberia-action/cyberia-action.service.js +20 -4
  65. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +58 -57
  66. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +37 -6
  67. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +8 -2
  68. package/src/api/cyberia-entity/cyberia-entity.router.js +39 -7
  69. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +74 -0
  70. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +67 -0
  71. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +63 -0
  72. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +46 -0
  73. package/src/api/cyberia-instance/cyberia-fallback-world.js +62 -10
  74. package/src/api/cyberia-instance/cyberia-instance.model.js +32 -2
  75. package/src/api/cyberia-instance/cyberia-instance.router.js +6 -6
  76. package/src/api/cyberia-instance/cyberia-world-generator.js +116 -3
  77. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +3 -0
  78. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +39 -7
  79. package/src/api/cyberia-map/cyberia-map.router.js +21 -6
  80. package/src/api/cyberia-quest/cyberia-quest.controller.js +38 -0
  81. package/src/api/cyberia-quest/cyberia-quest.router.js +47 -7
  82. package/src/api/cyberia-quest/cyberia-quest.service.js +59 -4
  83. package/src/api/cyberia-saga/cyberia-saga.controller.js +74 -0
  84. package/src/api/cyberia-saga/cyberia-saga.model.js +59 -0
  85. package/src/api/cyberia-saga/cyberia-saga.router.js +63 -0
  86. package/src/api/cyberia-saga/cyberia-saga.service.js +42 -0
  87. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +551 -122
  88. package/src/api/cyberia-skill/cyberia-skill.controller.js +74 -0
  89. package/src/api/cyberia-skill/cyberia-skill.model.js +50 -0
  90. package/src/api/cyberia-skill/cyberia-skill.router.js +63 -0
  91. package/src/api/cyberia-skill/cyberia-skill.service.js +42 -0
  92. package/src/api/ipfs/ipfs.service.js +28 -15
  93. package/src/api/object-layer/object-layer.router.js +25 -15
  94. package/src/api/object-layer/object-layer.service.js +15 -20
  95. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +39 -7
  96. package/src/cli/baremetal.js +717 -16
  97. package/src/cli/cluster.js +80 -5
  98. package/src/cli/deploy.js +326 -292
  99. package/src/cli/docker-compose.js +648 -0
  100. package/src/cli/env.js +12 -6
  101. package/src/cli/fs.js +75 -30
  102. package/src/cli/image.js +187 -55
  103. package/src/cli/index.js +157 -6
  104. package/src/cli/kickstart.js +142 -20
  105. package/src/cli/monitor.js +387 -6
  106. package/src/cli/release.js +72 -15
  107. package/src/cli/repository.js +339 -40
  108. package/src/cli/run.js +678 -186
  109. package/src/cli/secrets.js +82 -48
  110. package/src/cli/ssh.js +234 -0
  111. package/src/cli/static.js +2 -2
  112. package/src/client/components/core/PanelForm.js +44 -44
  113. package/src/client/components/cyberia/ActionEngineCyberia.js +1867 -0
  114. package/src/client/components/cyberia/EntityEngineCyberia.js +585 -0
  115. package/src/client/components/cyberia/InstanceEngineCyberia.js +219 -13
  116. package/src/client/components/cyberia/MapEngineCyberia.js +154 -71
  117. package/src/client/components/cyberia/ObjectLayerEngineModal.js +40 -63
  118. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +34 -20
  119. package/src/client/components/cyberia/SharedDefaultsCyberia.js +195 -4
  120. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +66 -0
  121. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +8 -0
  122. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -0
  123. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +92 -20
  124. package/src/client/public/cyberia-docs/ARCHITECTURE.md +272 -50
  125. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +3 -3
  126. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +39 -22
  127. package/src/client/public/cyberia-docs/CYBERIA-LORE.md +88 -0
  128. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +394 -0
  129. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +28 -12
  130. package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
  131. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +27 -5
  132. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  133. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  134. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +99 -0
  135. package/src/client/services/cyberia-instance/cyberia-instance.management.js +2 -2
  136. package/src/client/services/cyberia-map/cyberia-map.management.js +2 -2
  137. package/src/client/services/cyberia-saga/cyberia-saga.service.js +99 -0
  138. package/src/client/services/cyberia-skill/cyberia-skill.service.js +99 -0
  139. package/src/client/services/object-layer/object-layer.management.js +6 -6
  140. package/src/{server → client-builder}/client-build-docs.js +15 -5
  141. package/src/{server → client-builder}/client-build-live.js +3 -3
  142. package/src/{server → client-builder}/client-build.js +25 -22
  143. package/src/{server → client-builder}/client-dev-server.js +3 -3
  144. package/src/{server → client-builder}/client-icons.js +2 -2
  145. package/src/{server → client-builder}/ssr.js +5 -5
  146. package/src/client.build.js +1 -3
  147. package/src/client.dev.js +1 -1
  148. package/src/db/mongo/MongoBootstrap.js +12 -12
  149. package/src/db/mongo/MongooseDB.js +2 -1
  150. package/src/grpc/cyberia/grpc-server.js +255 -70
  151. package/src/index.js +12 -1
  152. package/src/mailer/EmailRender.js +1 -1
  153. package/src/{server → projects/cyberia}/atlas-sprite-sheet-generator.js +2 -2
  154. package/src/{server → projects/cyberia}/besu-genesis-generator.js +3 -3
  155. package/src/projects/cyberia/catalog-cyberia.js +81 -0
  156. package/src/projects/cyberia/gemini-client.js +175 -0
  157. package/src/projects/cyberia/generate-saga.js +2107 -0
  158. package/src/{server → projects/cyberia}/ipfs-client.js +6 -4
  159. package/src/{server → projects/cyberia}/object-layer.js +12 -108
  160. package/src/{server → projects/cyberia}/semantic-layer-generator-floor.js +1 -1
  161. package/src/{server → projects/cyberia}/semantic-layer-generator-resource.js +1 -1
  162. package/src/{server → projects/cyberia}/semantic-layer-generator-skin.js +1 -1
  163. package/src/{server → projects/cyberia}/semantic-layer-generator.js +2 -2
  164. package/src/{server → projects/cyberia}/shape-generator.js +2 -2
  165. package/src/projects/underpost/catalog-underpost.js +60 -0
  166. package/src/runtime/cyberia-client/Dockerfile +31 -83
  167. package/src/runtime/cyberia-client/Dockerfile.dev +23 -30
  168. package/src/runtime/cyberia-server/Dockerfile +23 -42
  169. package/src/runtime/cyberia-server/Dockerfile.dev +20 -35
  170. package/src/runtime/engine-cyberia/Dockerfile +143 -0
  171. package/src/runtime/engine-cyberia/Dockerfile.dev +143 -0
  172. package/src/runtime/engine-cyberia/Dockerfile.test +165 -0
  173. package/src/runtime/engine-cyberia/compose.env +131 -0
  174. package/src/runtime/engine-cyberia/docker-compose.yml +316 -0
  175. package/src/runtime/engine-cyberia/ipfs/configure-ipfs.sh +13 -0
  176. package/src/runtime/engine-cyberia/mongodb/entrypoint.sh +76 -0
  177. package/src/runtime/engine-cyberia/nginx.conf +87 -0
  178. package/src/runtime/express/Express.js +2 -2
  179. package/src/runtime/nginx/Nginx.js +250 -0
  180. package/src/runtime/wp/Dockerfile +3 -3
  181. package/src/server/catalog.js +72 -0
  182. package/src/server/conf.js +415 -60
  183. package/src/server/runtime-status.js +252 -0
  184. package/src/server/start.js +42 -11
  185. package/src/server.js +6 -2
  186. package/test/cyberia-instance-conf-defaults.test.js +140 -0
  187. package/test/deploy-monitor.test.js +267 -0
  188. package/test/shape-generator.test.js +7 -1
  189. package/typedoc.dd-cyberia.json +3 -1
  190. package/typedoc.json +3 -1
  191. package/manifests/deployment/dd-test-development/deployment.yaml +0 -256
  192. package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
  193. /package/src/client/ssr/{Render.js → RootDocument.js} +0 -0
  194. /package/src/{server → client-builder}/client-formatted.js +0 -0
@@ -10,6 +10,7 @@ import dotenv from 'dotenv';
10
10
  import {
11
11
  capFirst,
12
12
  getCapVariableName,
13
+ getDirname,
13
14
  newInstance,
14
15
  orderAbc,
15
16
  orderArrayFromAttrInt,
@@ -475,9 +476,6 @@ const loadConf = (deployId = DEFAULT_DEPLOY_ID, subConf) => {
475
476
  fs.removeSync(`${path}/.env.production`);
476
477
  fs.removeSync(`${path}/.env.development`);
477
478
  fs.removeSync(`${path}/.env.test`);
478
- if (fs.existsSync(`${path}/typedoc.json`)) shellExec(`git checkout ${path}/typedoc.json`);
479
- shellExec(`git checkout ${path}/package.json`);
480
- shellExec(`git checkout ${path}/package-lock.json`);
481
479
  return;
482
480
  }
483
481
  const folder = getConfFolder(deployId);
@@ -1085,13 +1083,9 @@ const buildPortProxyRouter = (
1085
1083
 
1086
1084
  if (Object.keys(router).length === 0) return router;
1087
1085
 
1088
- if (options.devProxyContext === true && process.env.NODE_ENV === 'development') {
1089
- const confDevApiServer = JSON.parse(
1090
- fs.readFileSync(
1091
- `./engine-private/conf/${process.argv[3]}/conf.server.dev.${process.argv[4]}-dev-api.json`,
1092
- 'utf8',
1093
- ),
1094
- );
1086
+ const devApiConfPath = `./engine-private/conf/${process.argv[3]}/conf.server.dev.${process.argv[4]}-dev-api.json`;
1087
+ if (options.devProxyContext === true && process.env.NODE_ENV === 'development' && fs.existsSync(devApiConfPath)) {
1088
+ const confDevApiServer = JSON.parse(fs.readFileSync(devApiConfPath, 'utf8'));
1095
1089
  let devApiHosts = [];
1096
1090
  let origins = [];
1097
1091
  for (const _host of Object.keys(confDevApiServer))
@@ -1297,15 +1291,17 @@ const validateTemplatePath = (absolutePath = '') => {
1297
1291
  * @description Waits for the deploy monitor.
1298
1292
  * @param {boolean} [isFinal=false] - If true, logs when the final (non-replica) deployment completes.
1299
1293
  * @param {number} [deltaMs=1000] - The delta ms.
1300
- * @returns {Promise<void>} - The await deploy monitor.
1294
+ * @param {boolean} [callback=false] - The callback.
1295
+ * @returns {Promise<boolean>} - `false` if `container-status=error` was detected, `true` on clean completion.
1301
1296
  * @memberof ServerConfBuilder
1302
1297
  */
1303
- const awaitDeployMonitor = async (isFinal = false, deltaMs = 1000) => {
1304
- Underpost.env.set('await-deploy', new Date().toISOString());
1298
+ const awaitDeployMonitor = async (isFinal = false, deltaMs = 1000, callback = false) => {
1299
+ if (!callback) Underpost.env.set('await-deploy', new Date().toISOString());
1305
1300
  if (isFinal) logger.info('Final deployment running (no replica)');
1306
1301
  await timer(deltaMs);
1307
- if (Underpost.env.get('container-status') === 'error') throw new Error('Container status error');
1308
- if (Underpost.env.get('await-deploy')) return await awaitDeployMonitor(isFinal, deltaMs);
1302
+ if (Underpost.env.get('container-status') === 'error') return false;
1303
+ if (Underpost.env.get('await-deploy')) return await awaitDeployMonitor(false, deltaMs, true);
1304
+ return true;
1309
1305
  };
1310
1306
 
1311
1307
  /**
@@ -1338,7 +1334,7 @@ const mergeFile = async (parts = [], outputFilePath) => {
1338
1334
  * @memberof ServerConfBuilder
1339
1335
  */
1340
1336
  const getPathsSSR = (conf) => {
1341
- const paths = ['src/client/ssr/Render.js'];
1337
+ const paths = ['src/client/ssr/RootDocument.js'];
1342
1338
  for (const o of conf.head) paths.push(`src/client/ssr/head/${o}.js`);
1343
1339
  for (const o of conf.body) paths.push(`src/client/ssr/body/${o}.js`);
1344
1340
  for (const o of Object.keys(conf.mailer)) paths.push(`src/client/ssr/mailer/${conf.mailer[o]}.js`);
@@ -1421,65 +1417,131 @@ const writeEnv = (envPath, envObj) =>
1421
1417
 
1422
1418
  /**
1423
1419
  * @method buildCliDoc
1424
- * @description Builds the CLI documentation.
1425
- * @param {object} program - The program.
1426
- * @param {string} oldVersion - The old version.
1427
- * @param {string} newVersion - The new version.
1420
+ * @description Scrapes `node bin help` (and `node bin help <command>` for every
1421
+ * registered command) and renders a structured Markdown reference: a command
1422
+ * index with anchor links, plus a per-command section with its description,
1423
+ * usage, and Arguments/Options rendered as tables. Writes
1424
+ * `CLI-HELP.md` + the served reference doc, and refreshes the README CLI index.
1425
+ * @param {object} program - The commander program.
1426
+ * @param {string} oldVersion - The old version string to replace.
1427
+ * @param {string} newVersion - The new version string.
1428
1428
  * @memberof ServerConfBuilder
1429
1429
  */
1430
1430
  const buildCliDoc = (program, oldVersion, newVersion) => {
1431
- let md = shellExec(`node bin help`, { silent: true, stdout: true }).split('Options:');
1432
- const baseOptions =
1433
- `## ${md[0].split(`\n`)[2]}
1434
-
1435
- ### Usage: ` +
1436
- '`' +
1437
- md[0].split(`\n`)[0].split('Usage: ')[1] +
1438
- '`' +
1439
- `
1440
- ` +
1441
- '```\n Options:' +
1442
- md[1] +
1443
- ' \n```';
1444
- md =
1445
- baseOptions +
1446
- `
1447
-
1448
- ## Commands:
1449
- `;
1450
- program.commands.map((o) => {
1451
- md +=
1452
- `
1453
-
1454
- ` +
1455
- '### `' +
1456
- o._name +
1457
- '` :' +
1458
- `
1459
- ` +
1460
- '```\n ' +
1461
- shellExec(`node bin help ${o._name}`, { silent: true, stdout: true }) +
1462
- ' \n```' +
1463
- `
1464
- `;
1465
- });
1466
- md = md.replaceAll(oldVersion, newVersion);
1431
+ const help = (args = '') => shellExec(`node bin help${args ? ` ${args}` : ''}`, { silent: true, stdout: true });
1432
+ // Escape table-breaking pipes and collapse wrapped whitespace for a Markdown cell.
1433
+ const cell = (s) => String(s).replace(/\s+/g, ' ').replaceAll('|', '\\|').trim();
1434
+ const anchor = (name) => `underpost-${name}`.toLowerCase().replace(/[^a-z0-9-]/g, '');
1435
+
1436
+ // Parse a commander help block into { usage, description, sections: { Options, Arguments, Commands } }.
1437
+ const parseHelp = (text) => {
1438
+ const lines = text.split('\n');
1439
+ const usageMatch = lines[0].match(/^Usage:\s*(.*)$/);
1440
+ const usage = usageMatch ? usageMatch[1].trim() : '';
1441
+ const sections = {};
1442
+ const descLines = [];
1443
+ let current = null;
1444
+ let buf = [];
1445
+ const flush = () => {
1446
+ if (current) sections[current] = buf.join('\n');
1447
+ buf = [];
1448
+ };
1449
+ for (let i = 1; i < lines.length; i++) {
1450
+ const line = lines[i];
1451
+ const head = line.match(/^([A-Za-z][\w ]*):\s*$/); // top-level "Options:", "Arguments:", "Commands:"
1452
+ if (head) {
1453
+ flush();
1454
+ current = head[1].trim();
1455
+ } else if (current !== null) {
1456
+ buf.push(line);
1457
+ } else {
1458
+ descLines.push(line);
1459
+ }
1460
+ }
1461
+ flush();
1462
+ return { usage, description: descLines.join('\n').trim(), sections };
1463
+ };
1464
+
1465
+ // Parse a columnar " <term> <description>" section (descriptions may wrap onto
1466
+ // indented continuation lines) into [{ term, desc }].
1467
+ const parseEntries = (text = '') => {
1468
+ const entries = [];
1469
+ for (const line of text.split('\n')) {
1470
+ if (!line.trim()) continue;
1471
+ const leading = line.length - line.trimStart().length;
1472
+ if (leading <= 2) {
1473
+ const rest = line.trim();
1474
+ const gap = rest.search(/\s{2,}/);
1475
+ entries.push(gap === -1 ? { term: rest, desc: '' } : { term: rest.slice(0, gap), desc: rest.slice(gap) });
1476
+ } else if (entries.length) {
1477
+ entries[entries.length - 1].desc += ` ${line.trim()}`;
1478
+ }
1479
+ }
1480
+ return entries;
1481
+ };
1482
+
1483
+ const table = (head, entries) =>
1484
+ !entries.length
1485
+ ? ''
1486
+ : `| ${head[0]} | ${head[1]} |\n| --- | --- |\n` +
1487
+ entries.map(({ term, desc }) => `| \`${cell(term)}\` | ${cell(desc)} |`).join('\n') +
1488
+ '\n';
1489
+
1490
+ const detailSection = (sections, name, head) => {
1491
+ const t = table(head, parseEntries(sections[name]));
1492
+ return t ? `\n#### ${name}\n\n${t}` : '';
1493
+ };
1494
+
1495
+ // ── Top-level index ──
1496
+ const root = parseHelp(help());
1497
+ const commandEntries = parseEntries(root.sections['Commands']).filter((e) => e.term.split(' ')[0] !== 'help');
1498
+
1499
+ const index =
1500
+ `## Underpost CLI\n\n` +
1501
+ (root.description ? `> ${root.description.replace(/\s+/g, ' ')}\n\n` : '') +
1502
+ `**Usage:** \`${root.usage}\`\n\n` +
1503
+ `### Global options\n\n${table(['Option', 'Description'], parseEntries(root.sections['Options']))}\n` +
1504
+ `### Commands\n\n| Command | Description |\n| --- | --- |\n` +
1505
+ commandEntries
1506
+ .map((e) => {
1507
+ const name = e.term.split(' ')[0];
1508
+ return `| [\`${name}\`](#${anchor(name)}) | ${cell(e.desc)} |`;
1509
+ })
1510
+ .join('\n') +
1511
+ '\n';
1512
+
1513
+ // ── Per-command detail ──
1514
+ let details = `\n## Command reference\n`;
1515
+ for (const cmd of program.commands) {
1516
+ const name = cmd._name;
1517
+ if (name === 'help') continue;
1518
+ const cmdHelp = parseHelp(help(name));
1519
+ details +=
1520
+ `\n### underpost ${name}\n\n` +
1521
+ (cmdHelp.description ? `${cmdHelp.description.replace(/\s+/g, ' ')}\n\n` : '') +
1522
+ `**Usage:** \`${cmdHelp.usage}\`\n` +
1523
+ detailSection(cmdHelp.sections, 'Arguments', ['Argument', 'Description']) +
1524
+ detailSection(cmdHelp.sections, 'Options', ['Option', 'Description']) +
1525
+ `\n---\n`;
1526
+ }
1527
+
1528
+ const md = `${index}${details}`.replaceAll(oldVersion, newVersion);
1467
1529
  fs.writeFileSync(`./src/client/public/nexodev/docs/references/Command Line Interface.md`, md, 'utf8');
1468
1530
  fs.writeFileSync(`./CLI-HELP.md`, md, 'utf8');
1469
1531
 
1470
- // Update README.md: replace version and CLI index section between comment tags
1471
- let readme = fs.readFileSync(`./README.md`, 'utf8');
1472
- readme = readme.replaceAll(oldVersion, newVersion);
1532
+ // Update README.md: bump version and refresh the CLI index between the comment tags.
1533
+ let readme = fs.readFileSync(`./README.md`, 'utf8').replaceAll(oldVersion, newVersion);
1473
1534
  const cliStartTag = '<!-- cli-index-start -->';
1474
1535
  const cliEndTag = '<!-- cli-index-end -->';
1475
1536
  const startIdx = readme.indexOf(cliStartTag);
1476
1537
  const endIdx = readme.indexOf(cliEndTag);
1477
1538
  if (startIdx !== -1 && endIdx !== -1) {
1539
+ const readmeIndex = index.replace(/\(#(underpost-[a-z0-9-]+)\)/g, '(CLI-HELP.md#$1)');
1478
1540
  readme =
1479
1541
  readme.substring(0, startIdx) +
1480
1542
  cliStartTag +
1481
1543
  '\n' +
1482
- baseOptions +
1544
+ readmeIndex.replaceAll(oldVersion, newVersion) +
1483
1545
  '\n' +
1484
1546
  cliEndTag +
1485
1547
  readme.substring(endIdx + cliEndTag.length);
@@ -1748,6 +1810,293 @@ ${renderHosts}`,
1748
1810
  return { renderHosts };
1749
1811
  };
1750
1812
 
1813
+ /**
1814
+ * Resolves the concrete deploy ids a build or conf-sync run should iterate over.
1815
+ *
1816
+ * The meta deploy id `dd` fans out to the comma separated ids declared in
1817
+ * `engine-private/deploy/dd.router`; any other value is parsed as a comma separated list.
1818
+ * Entries are trimmed and empties dropped.
1819
+ *
1820
+ * @method resolveDeployList
1821
+ * @param {string} deployId - A deploy id, a comma separated list, or the `dd` meta id.
1822
+ * @returns {string[]} Ordered list of concrete deploy ids.
1823
+ * @memberof ServerConfBuilder
1824
+ */
1825
+ const resolveDeployList = (deployId) =>
1826
+ (deployId === 'dd' ? fs.readFileSync('./engine-private/deploy/dd.router', 'utf8') : deployId)
1827
+ .split(',')
1828
+ .map((id) => id.trim())
1829
+ .filter(Boolean);
1830
+
1831
+ /**
1832
+ * Syncs a single deploy id's private configuration into its dedicated
1833
+ * `engine-<suffix>-private` repository and pushes the result.
1834
+ *
1835
+ * Idempotent and safe to rerun: the private repo is cloned when missing or reset to a clean
1836
+ * checkout when present, then the deploy id's `conf` folder, matching `replica` and
1837
+ * `itc-scripts` entries, and any caller-supplied `extraPaths` payloads are mirrored. The
1838
+ * commit/push step is a no-op when nothing changed (`silentOnError`).
1839
+ *
1840
+ * @method syncPrivateConf
1841
+ * @param {string} deployId - A concrete deploy id (e.g. `dd-cyberia`), not the `dd` meta id.
1842
+ * @param {string[]} [extraPaths=[]] - Extra `./engine-private` payload paths to mirror (from the
1843
+ * deploy's product catalog), kept out of this module so it stays product-agnostic.
1844
+ * @returns {void}
1845
+ * @memberof ServerConfBuilder
1846
+ */
1847
+ const syncPrivateConf = (deployId, extraPaths = []) => {
1848
+ const suffix = deployId.split('dd-')[1];
1849
+ const privateRepoName = `engine-${suffix}-private`;
1850
+ const privateGitUri = `${process.env.GITHUB_USERNAME}/${privateRepoName}`;
1851
+ const privateRepoPath = `../${privateRepoName}`;
1852
+
1853
+ if (!fs.existsSync(privateRepoPath)) {
1854
+ shellExec(`cd .. && underpost clone ${privateGitUri}`, { silent: true });
1855
+ } else {
1856
+ shellExec(`git config --global --add safe.directory '${dir.resolve(privateRepoPath)}'`);
1857
+ shellExec(`cd ${privateRepoPath} && git checkout . && git clean -f -d && underpost pull . ${privateGitUri}`, {
1858
+ silent: true,
1859
+ });
1860
+ }
1861
+
1862
+ const confDest = `${privateRepoPath}/conf/${deployId}`;
1863
+ fs.removeSync(confDest);
1864
+ fs.mkdirSync(confDest, { recursive: true });
1865
+ fs.copySync(`./engine-private/conf/${deployId}`, confDest);
1866
+
1867
+ fs.removeSync(`${privateRepoPath}/replica`);
1868
+ for (const payloadDir of ['replica', 'itc-scripts']) {
1869
+ const srcDir = `./engine-private/${payloadDir}`;
1870
+ if (!fs.existsSync(srcDir)) continue;
1871
+ for (const entry of fs.readdirSync(srcDir))
1872
+ if (entry.match(deployId)) fs.copySync(`${srcDir}/${entry}`, `${privateRepoPath}/${payloadDir}/${entry}`);
1873
+ }
1874
+
1875
+ for (const extraPath of extraPaths) fs.copySync(`./engine-private/${extraPath}`, `${privateRepoPath}/${extraPath}`);
1876
+
1877
+ shellExec(
1878
+ `cd ${privateRepoPath}` +
1879
+ ` && git add .` +
1880
+ ` && underpost cmt . ci engine-core-conf 'Update ${deployId} conf'` +
1881
+ ` && underpost push . ${privateGitUri}`,
1882
+ { silent: true, silentOnError: true },
1883
+ );
1884
+ };
1885
+
1886
+ /**
1887
+ * Moves a deploy's public template sources into the engine working tree ahead of
1888
+ * the build copy step. Idempotent and safe to rerun: each move is guarded by
1889
+ * `existsSync`, so already-moved or absent sources are skipped rather than throwing.
1890
+ * The `[src, dest]` pairs come from the deploy's product catalog (passed in), so
1891
+ * this module stays product-agnostic.
1892
+ *
1893
+ * @method syncDeployIdSources
1894
+ * @param {Array<[string, string]>} [sourceMoves=[]] - Public `[src, dest]` move pairs.
1895
+ * @returns {boolean} `true` when any sources were declared, else `false`.
1896
+ * @memberof ServerConfBuilder
1897
+ */
1898
+ const syncDeployIdSources = (sourceMoves = []) => {
1899
+ if (!sourceMoves.length) return false;
1900
+ for (const dir of ['src/api', 'src/client/components', 'src/client/public', 'src/client/services'])
1901
+ fs.mkdirSync(dir, { recursive: true });
1902
+ for (const [src, dest] of sourceMoves) if (fs.existsSync(src)) fs.moveSync(src, dest, { overwrite: true });
1903
+ return true;
1904
+ };
1905
+
1906
+ /**
1907
+ * Rebuilds the standalone `pwa-microservices-template` from scratch out of the current
1908
+ * engine source tree.
1909
+ *
1910
+ * Clones the template repo next to the engine when missing, otherwise resets it to a clean
1911
+ * pristine checkout, then syncs every engine-tracked file the template is allowed to carry
1912
+ * ({@link validateTemplatePath}), strips engine-only + product modules, restores the template's
1913
+ * own CI workflows + guest services, and rewrites `package.json` / `package-lock.json` / `README`
1914
+ * so the result is a standalone, installable project. Throws on failure; callers own exit codes.
1915
+ *
1916
+ * Product catalogs are read dynamically ({@link module:src/server/catalog} `loadProductCatalogs`),
1917
+ * so this stays decoupled from — and survives removal of — any product module.
1918
+ *
1919
+ * @method buildTemplate
1920
+ * @param {object} [options]
1921
+ * @param {string} [options.srcPath='./'] - Engine source root to sync from.
1922
+ * @param {string} [options.toPath='../pwa-microservices-template'] - Template output path.
1923
+ * @returns {Promise<void>}
1924
+ * @memberof ServerConfBuilder
1925
+ */
1926
+ const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-template' } = {}) => {
1927
+ const walk = (await import('ignore-walk')).default;
1928
+ const { TEMPLATE_RESTORE_PATHS, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION } =
1929
+ await import('../projects/underpost/catalog-underpost.js');
1930
+ const { loadProductCatalogs } = await import('./catalog.js');
1931
+ const githubUsername = process.env.GITHUB_USERNAME;
1932
+
1933
+ logger.info('Build template', { srcPath, toPath });
1934
+
1935
+ const sourceFiles = (
1936
+ await new Promise((resolve) =>
1937
+ walk({ path: srcPath, ignoreFiles: [`.gitignore`], includeEmpty: false, follow: false }, (...args) =>
1938
+ resolve(args[1]),
1939
+ ),
1940
+ )
1941
+ ).filter((p) => !p.startsWith('.git'));
1942
+
1943
+ fs.removeSync(`${githubUsername}/pwa-microservices-template`);
1944
+ shellExec(`cd .. && node engine/bin clone ${githubUsername}/pwa-microservices-template`);
1945
+
1946
+ shellExec(`cd ${toPath} && git config core.filemode false`);
1947
+
1948
+ for (const copyPath of sourceFiles) {
1949
+ if (copyPath === 'NaN') continue;
1950
+ const absolutePath = `${srcPath}/${copyPath}`;
1951
+ if (!validateTemplatePath(absolutePath)) continue;
1952
+
1953
+ const folder = getDirname(`${toPath}/${copyPath}`);
1954
+ if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
1955
+
1956
+ logger.info('build', `${toPath}/${copyPath}`);
1957
+ fs.copyFileSync(absolutePath, `${toPath}/${copyPath}`);
1958
+ }
1959
+
1960
+ fs.copySync(`./.vscode`, `${toPath}/.vscode`);
1961
+ fs.copySync(`./src/client/public/default`, `${toPath}/src/client/public/default`);
1962
+
1963
+ // Preserve the template's own README + package.json identity before merging engine metadata.
1964
+ for (const checkoutPath of ['README.md', 'package.json']) shellExec(`cd ${toPath} && git checkout ${checkoutPath}`);
1965
+
1966
+ // Strip each product catalog's `stripPaths` (aggregated dynamically) plus the engine-only
1967
+ // workflows, deploy manifests, and product catalog modules.
1968
+ const productStripPaths = (await loadProductCatalogs()).flatMap((c) => c.stripPaths);
1969
+ for (const deletePath of productStripPaths) {
1970
+ const target = `${toPath}/${deletePath}`;
1971
+ if (fs.existsSync(target)) fs.removeSync(target);
1972
+ }
1973
+ shellExec(`rm -rf ${toPath}/.github`);
1974
+ shellExec(`rm -rf ${toPath}/manifests/deployment/dd-*`);
1975
+
1976
+ fs.mkdirSync(`${toPath}/.github/workflows`, { recursive: true });
1977
+ for (const restorePath of TEMPLATE_RESTORE_PATHS) {
1978
+ const dest = `${toPath}/${restorePath}`;
1979
+ if (fs.statSync(restorePath).isDirectory()) fs.copySync(restorePath, dest, { overwrite: true });
1980
+ else fs.copyFileSync(restorePath, dest);
1981
+ }
1982
+
1983
+ // ── package.json: take engine deps/scripts/version, keep template identity. ──
1984
+ const originPackageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
1985
+ const templatePackageJson = JSON.parse(fs.readFileSync(`${toPath}/package.json`, 'utf8'));
1986
+ const templateName = templatePackageJson.name;
1987
+
1988
+ templatePackageJson.dependencies = originPackageJson.dependencies;
1989
+ templatePackageJson.devDependencies = originPackageJson.devDependencies;
1990
+ templatePackageJson.version = originPackageJson.version;
1991
+ templatePackageJson.scripts = originPackageJson.scripts;
1992
+ templatePackageJson.overrides = originPackageJson.overrides;
1993
+ templatePackageJson.name = templateName;
1994
+ templatePackageJson.description = TEMPLATE_DESCRIPTION;
1995
+ templatePackageJson.keywords = TEMPLATE_KEYWORDS;
1996
+ delete templatePackageJson.scripts['build:template'];
1997
+ fs.writeFileSync(`${toPath}/package.json`, JSON.stringify(templatePackageJson, null, 4), 'utf8');
1998
+
1999
+ // ── package-lock.json: mirror engine packages, keep template name/version on the root entry. ──
2000
+ const originPackageLockJson = JSON.parse(fs.readFileSync('./package-lock.json', 'utf8'));
2001
+ const templatePackageLockJson = JSON.parse(fs.readFileSync(`${toPath}/package-lock.json`, 'utf8'));
2002
+ const originBasePackageLock = newInstance(templatePackageLockJson.packages['']);
2003
+ templatePackageLockJson.name = templateName;
2004
+ templatePackageLockJson.version = originPackageLockJson.version;
2005
+ templatePackageLockJson.packages = originPackageLockJson.packages;
2006
+ templatePackageLockJson.packages[''].name = templateName;
2007
+ templatePackageLockJson.packages[''].version = originPackageLockJson.version;
2008
+ templatePackageLockJson.packages[''].hasInstallScript = originBasePackageLock.hasInstallScript;
2009
+ templatePackageLockJson.packages[''].license = originBasePackageLock.license;
2010
+ fs.writeFileSync(`${toPath}/package-lock.json`, JSON.stringify(templatePackageLockJson, null, 4), 'utf8');
2011
+
2012
+ fs.writeFileSync(
2013
+ `${toPath}/README.md`,
2014
+ fs
2015
+ .readFileSync('./README.md', 'utf8')
2016
+ .replace('<!-- template-title -->', '#### Base template for pwa/api-rest projects.'),
2017
+ 'utf8',
2018
+ );
2019
+ };
2020
+
2021
+ const updatePrivateTemplateRepo = async () => {
2022
+ const templatePath = '/home/dd/pwa-microservices-template';
2023
+ shellExec(`sudo rm -rf ${templatePath}
2024
+ cd /home/dd/engine && npm run build:template
2025
+ cd /home/dd
2026
+ underpost clone --bare underpostnet/pwa-microservices-template-private
2027
+ sudo rm -rf ${templatePath}/.git
2028
+ mv ./pwa-microservices-template-private.git ${templatePath}/.git
2029
+ cd ${templatePath}
2030
+ npm install --omit=dev --ignore-scripts
2031
+ git init
2032
+ git config user.name 'underpostnet'
2033
+ git config user.email 'development@underpost.net'
2034
+ git add .`);
2035
+ const hasChanges = shellExec(`node bin cmt ${templatePath} --has-changes`, {
2036
+ stdout: true,
2037
+ silent: true,
2038
+ disableLog: true,
2039
+ }).trim();
2040
+ if (hasChanges === '1') {
2041
+ shellExec(
2042
+ `cd ${templatePath} && git commit -m 'Update template' && underpost push . underpostnet/pwa-microservices-template-private`,
2043
+ );
2044
+ }
2045
+ };
2046
+
2047
+ /**
2048
+ * @method updatePrivateEngineTestRepo
2049
+ * @description Publishes a deploy id's freshly assembled template to its private
2050
+ * **test** source repo `engine-test-<idPart>` (separate from the production
2051
+ * `engine-<idPart>`). A pod started with `underpost start --build --private-test-repo`
2052
+ * clones this repo, so work-in-progress engine source can be tested end to end
2053
+ * without touching the production source. Mirrors {@link updatePrivateTemplateRepo}
2054
+ * but per-deploy-id and against the test repo.
2055
+ *
2056
+ * Assumes the deploy id template has already been assembled at the template path
2057
+ * (run `node bin/build <deployId>` first, or use `node bin/build <deployId> --update-private`).
2058
+ * @param {string} deployId - Concrete deploy id (e.g. `dd-core`).
2059
+ * @returns {Promise<void>}
2060
+ * @memberof ServerConfBuilder
2061
+ */
2062
+ const updatePrivateEngineTestRepo = async (deployId) => {
2063
+ const username = process.env.GITHUB_USERNAME || 'underpostnet';
2064
+ const repoName = `engine-test-${deployId.split('-')[1]}`;
2065
+ const templatePath = '/home/dd/pwa-microservices-template';
2066
+ if (!fs.existsSync(templatePath))
2067
+ throw new Error(`updatePrivateEngineTestRepo: assemble the template first (node bin/build ${deployId})`);
2068
+
2069
+ // Detach the assembled working tree from any engine-build git history.
2070
+ shellExec(`sudo rm -rf ${templatePath}/.git`);
2071
+
2072
+ // Adopt the test repo's existing history when present (so the push is a delta);
2073
+ // otherwise publish a fresh history on first push.
2074
+ shellExec(`cd /home/dd && sudo rm -rf ./${repoName}.git && underpost clone --bare ${username}/${repoName}`, {
2075
+ silent: true,
2076
+ disableLog: true,
2077
+ silentOnError: true,
2078
+ });
2079
+ if (fs.existsSync(`/home/dd/${repoName}.git`)) shellExec(`mv /home/dd/${repoName}.git ${templatePath}/.git`);
2080
+
2081
+ // `git init` converts the moved bare repo into a normal work-tree repo (bare
2082
+ // clones have no work tree, so `git add` would fail), and bootstraps a fresh
2083
+ // repo on first publish. Idempotent — mirrors updatePrivateTemplateRepo.
2084
+ shellExec(`cd ${templatePath}
2085
+ git init
2086
+ git config user.name '${username}'
2087
+ git config user.email 'development@underpost.net'
2088
+ git add .`);
2089
+
2090
+ const hasChanges = shellExec(`node bin cmt ${templatePath} --has-changes`, {
2091
+ stdout: true,
2092
+ silent: true,
2093
+ disableLog: true,
2094
+ }).trim();
2095
+ if (hasChanges === '1')
2096
+ shellExec(`cd ${templatePath} && git commit -m 'Update ${repoName}' && underpost push . ${username}/${repoName}`);
2097
+ else logger.info('No changes to publish', { repoName });
2098
+ };
2099
+
1751
2100
  export {
1752
2101
  Config,
1753
2102
  loadConf,
@@ -1795,4 +2144,10 @@ export {
1795
2144
  loadCronDeployEnv,
1796
2145
  cronDeployIdResolve,
1797
2146
  etcHostFactory,
2147
+ resolveDeployList,
2148
+ syncPrivateConf,
2149
+ syncDeployIdSources,
2150
+ buildTemplate,
2151
+ updatePrivateTemplateRepo,
2152
+ updatePrivateEngineTestRepo,
1798
2153
  };