cyberia 3.0.3 → 3.1.3

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 (160) hide show
  1. package/{.env.production → .env.example} +20 -2
  2. package/.github/workflows/engine-cyberia.cd.yml +41 -10
  3. package/.github/workflows/engine-cyberia.ci.yml +53 -14
  4. package/.github/workflows/ghpkg.ci.yml +1 -1
  5. package/.github/workflows/gitlab.ci.yml +1 -1
  6. package/.github/workflows/hardhat.ci.yml +82 -0
  7. package/.github/workflows/npmpkg.ci.yml +37 -8
  8. package/.github/workflows/publish.ci.yml +5 -5
  9. package/.github/workflows/publish.cyberia.ci.yml +5 -5
  10. package/.github/workflows/pwa-microservices-template-page.cd.yml +3 -3
  11. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  12. package/.github/workflows/release.cd.yml +3 -2
  13. package/.vscode/extensions.json +9 -8
  14. package/.vscode/settings.json +3 -2
  15. package/CHANGELOG.md +211 -1
  16. package/CLI-HELP.md +78 -53
  17. package/WHITE-PAPER.md +1540 -0
  18. package/bin/build.js +16 -10
  19. package/bin/cyberia.js +861 -4
  20. package/bin/deploy.js +103 -270
  21. package/bin/file.js +2 -1
  22. package/bin/index.js +861 -4
  23. package/bin/vs.js +3 -3
  24. package/conf.js +105 -97
  25. package/deployment.yaml +148 -4
  26. package/hardhat/.env.example +31 -0
  27. package/hardhat/README.md +531 -0
  28. package/hardhat/WHITE-PAPER.md +1540 -0
  29. package/hardhat/contracts/ObjectLayerToken.sol +391 -0
  30. package/hardhat/deployments/.gitkeep +0 -0
  31. package/hardhat/deployments/hardhat-ObjectLayerToken.json +11 -0
  32. package/hardhat/hardhat.config.js +136 -0
  33. package/hardhat/ignition/modules/ObjectLayerToken.js +21 -0
  34. package/hardhat/networks/besu-object-layer.network.json +138 -0
  35. package/hardhat/package-lock.json +7628 -0
  36. package/hardhat/package.json +45 -0
  37. package/hardhat/scripts/deployObjectLayerToken.js +98 -0
  38. package/hardhat/test/ObjectLayerToken.js +590 -0
  39. package/jsdoc.dd-cyberia.json +59 -0
  40. package/jsdoc.json +20 -13
  41. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  42. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  43. package/manifests/deployment/dd-cyberia-development/deployment.yaml +490 -0
  44. package/manifests/deployment/dd-cyberia-development/proxy.yaml +261 -0
  45. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +132 -0
  46. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  47. package/manifests/deployment/dd-test-development/deployment.yaml +52 -52
  48. package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
  49. package/manifests/pv-pvc-dd.yaml +1 -1
  50. package/package.json +50 -44
  51. package/proxy.yaml +79 -7
  52. package/pv-pvc.yaml +132 -0
  53. package/scripts/k3s-node-setup.sh +1 -1
  54. package/scripts/ports-ls.sh +2 -0
  55. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +3 -1
  56. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +1 -2
  57. package/src/api/document/document.service.js +1 -1
  58. package/src/api/file/file.controller.js +3 -1
  59. package/src/api/file/file.service.js +28 -5
  60. package/src/api/ipfs/ipfs.service.js +2 -2
  61. package/src/api/object-layer/object-layer.controller.js +6 -2
  62. package/src/api/object-layer/object-layer.model.js +12 -8
  63. package/src/api/object-layer/object-layer.router.js +668 -42
  64. package/src/api/object-layer/object-layer.service.js +6 -7
  65. package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +1 -2
  66. package/src/api/user/user.router.js +10 -5
  67. package/src/api/user/user.service.js +7 -7
  68. package/src/cli/baremetal.js +6 -10
  69. package/src/cli/cloud-init.js +0 -3
  70. package/src/cli/db.js +54 -71
  71. package/src/cli/deploy.js +64 -12
  72. package/src/cli/env.js +5 -5
  73. package/src/cli/fs.js +0 -2
  74. package/src/cli/image.js +0 -3
  75. package/src/cli/index.js +35 -13
  76. package/src/cli/monitor.js +5 -6
  77. package/src/cli/repository.js +329 -46
  78. package/src/cli/run.js +180 -120
  79. package/src/cli/secrets.js +1 -3
  80. package/src/cli/ssh.js +1 -1
  81. package/src/client/Itemledger.index.js +1 -959
  82. package/src/client/components/core/AgGrid.js +20 -5
  83. package/src/client/components/core/Alert.js +2 -2
  84. package/src/client/components/core/Content.js +22 -3
  85. package/src/client/components/core/Docs.js +30 -6
  86. package/src/client/components/core/FileExplorer.js +71 -4
  87. package/src/client/components/core/Input.js +1 -1
  88. package/src/client/components/core/Modal.js +20 -6
  89. package/src/client/components/core/RichText.js +1 -2
  90. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +1 -0
  91. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +44 -2
  92. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +0 -1
  93. package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +64 -2
  94. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +1 -0
  95. package/src/client/components/underpost/CssUnderpost.js +59 -0
  96. package/src/client/components/underpost/LogInUnderpost.js +3 -0
  97. package/src/client/components/underpost/LogOutUnderpost.js +2 -0
  98. package/src/client/components/underpost/MenuUnderpost.js +99 -13
  99. package/src/client/components/underpost/RoutesUnderpost.js +2 -0
  100. package/src/client/public/cryptokoyn/assets/logo/base-icon.png +0 -0
  101. package/src/client/public/cryptokoyn/browserconfig.xml +12 -0
  102. package/src/client/public/cryptokoyn/microdata.json +85 -0
  103. package/src/client/public/cryptokoyn/site.webmanifest +57 -0
  104. package/src/client/public/cryptokoyn/sitemap +3 -3
  105. package/src/client/public/default/sitemap +3 -3
  106. package/src/client/public/itemledger/browserconfig.xml +2 -2
  107. package/src/client/public/itemledger/manifest.webmanifest +4 -4
  108. package/src/client/public/itemledger/microdata.json +71 -0
  109. package/src/client/public/itemledger/sitemap +3 -3
  110. package/src/client/public/itemledger/yandex-browser-manifest.json +2 -2
  111. package/src/client/public/test/sitemap +3 -3
  112. package/src/client/ssr/body/404.js +15 -11
  113. package/src/client/ssr/body/500.js +15 -11
  114. package/src/client/ssr/body/SwaggerDarkMode.js +285 -0
  115. package/src/client/ssr/head/PwaItemledger.js +60 -0
  116. package/src/client/ssr/offline/NoNetworkConnection.js +11 -10
  117. package/src/client/ssr/pages/Test.js +11 -10
  118. package/src/client.build.js +0 -3
  119. package/src/client.dev.js +0 -3
  120. package/src/db/DataBaseProvider.js +17 -2
  121. package/src/db/mariadb/MariaDB.js +14 -9
  122. package/src/db/mongo/MongooseDB.js +17 -1
  123. package/src/index.js +1 -1
  124. package/src/proxy.js +0 -3
  125. package/src/runtime/express/Express.js +15 -9
  126. package/src/runtime/lampp/Lampp.js +6 -13
  127. package/src/server/auth.js +12 -14
  128. package/src/server/backup.js +2 -3
  129. package/src/server/besu-genesis-generator.js +1630 -0
  130. package/src/server/client-build-docs.js +126 -17
  131. package/src/server/client-build-live.js +9 -18
  132. package/src/server/client-build.js +203 -75
  133. package/src/server/client-dev-server.js +14 -13
  134. package/src/server/conf.js +376 -164
  135. package/src/server/cron.js +2 -1
  136. package/src/server/dns.js +28 -12
  137. package/src/server/downloader.js +0 -2
  138. package/src/server/logger.js +27 -9
  139. package/src/server/object-layer.js +79 -6
  140. package/src/server/peer.js +0 -2
  141. package/src/server/process.js +1 -50
  142. package/src/server/proxy.js +4 -8
  143. package/src/server/runtime.js +5 -8
  144. package/src/server/ssr.js +0 -3
  145. package/src/server/start.js +19 -12
  146. package/src/server/tls.js +0 -2
  147. package/src/server.js +0 -4
  148. package/.env.development +0 -43
  149. package/.env.test +0 -43
  150. package/hardhat/contracts/CryptoKoyn.sol +0 -59
  151. package/hardhat/contracts/ItemLedger.sol +0 -73
  152. package/hardhat/contracts/Lock.sol +0 -34
  153. package/hardhat/hardhat.config.cjs +0 -45
  154. package/hardhat/ignition/modules/Lock.js +0 -18
  155. package/hardhat/networks/cryptokoyn-itemledger.network.json +0 -29
  156. package/hardhat/scripts/deployCryptokoyn.cjs +0 -25
  157. package/hardhat/scripts/deployItemledger.cjs +0 -25
  158. package/hardhat/test/Lock.js +0 -126
  159. package/hardhat/white-paper.md +0 -581
  160. package/white-paper.md +0 -581
@@ -4,18 +4,27 @@
4
4
  * @namespace UnderpostRepository
5
5
  */
6
6
 
7
- import { commitData } from '../client/components/core/CommonJs.js';
8
7
  import dotenv from 'dotenv';
8
+ import { commitData } from '../client/components/core/CommonJs.js';
9
9
  import { pbcopy, shellCd, shellExec } from '../server/process.js';
10
10
  import { actionInitLog, loggerFactory } from '../server/logger.js';
11
11
  import fs from 'fs-extra';
12
- import { getNpmRootPath } from '../server/conf.js';
13
- import { Config } from '../server/conf.js';
12
+ import {
13
+ getNpmRootPath,
14
+ Config,
15
+ loadConf,
16
+ readConfJson,
17
+ getConfFilePath,
18
+ loadReplicas,
19
+ loadConfServerJson,
20
+ getDataDeploy,
21
+ buildReplicaId,
22
+ writeEnv,
23
+ } from '../server/conf.js';
24
+ import { buildClient } from '../server/client-build.js';
14
25
  import { DefaultConf } from '../../conf.js';
15
26
  import Underpost from '../index.js';
16
27
 
17
- dotenv.config();
18
-
19
28
  const logger = loggerFactory(import.meta);
20
29
 
21
30
  const diffCmd = `--no-pager show -U0 -w --word-diff=color --word-diff-regex='[^[:space:]]' --color=always`;
@@ -37,8 +46,8 @@ class UnderpostRepository {
37
46
  * @param {boolean} [options.g8=false] - If true, uses the .g8 extension.
38
47
  * @memberof UnderpostRepository
39
48
  */
40
- clone(gitUri = `${process.env.GITHUB_USERNAME}/pwa-microservices-template`, options = { bare: false, G8: false }) {
41
- const gExtension = options.G8 === true ? '.g8' : '.git';
49
+ clone(gitUri = `${process.env.GITHUB_USERNAME}/pwa-microservices-template`, options = { bare: false, g8: false }) {
50
+ const gExtension = options.g8 === true ? '.g8' : '.git';
42
51
  const repoName = gitUri.split('/').pop();
43
52
  if (fs.existsSync(`./${repoName}`)) fs.removeSync(`./${repoName}`);
44
53
  shellExec(
@@ -54,16 +63,16 @@ class UnderpostRepository {
54
63
  * Pulls updates from a GitHub repository.
55
64
  * @param {string} [repoPath='./'] - The local path to the repository.
56
65
  * @param {string} [gitUri=`${process.env.GITHUB_USERNAME}/pwa-microservices-template`] - The URI of the GitHub repository.
57
- * @param {object} [options={ G8: false }] - Pulling options.
66
+ * @param {object} [options={ g8: false }] - Pulling options.
58
67
  * @param {boolean} [options.g8=false] - If true, uses the .g8 extension.
59
68
  * @memberof UnderpostRepository
60
69
  */
61
70
  pull(
62
71
  repoPath = './',
63
72
  gitUri = `${process.env.GITHUB_USERNAME}/pwa-microservices-template`,
64
- options = { G8: false },
73
+ options = { g8: false },
65
74
  ) {
66
- const gExtension = options.G8 === true ? '.g8' : '.git';
75
+ const gExtension = options.g8 === true ? '.g8' : '.git';
67
76
  shellExec(
68
77
  `cd ${repoPath} && git pull https://${
69
78
  process.env.GITHUB_TOKEN ? `${process.env.GITHUB_TOKEN}@` : ''
@@ -118,15 +127,24 @@ class UnderpostRepository {
118
127
  changelogBuild: false,
119
128
  changelogMinVersion: '',
120
129
  changelogNoHash: false,
130
+ b: false,
121
131
  },
122
132
  ) {
123
133
  if (!repoPath) repoPath = '.';
124
134
 
135
+ if (options.b) {
136
+ const currentBranch = shellExec(`cd ${repoPath} && git branch --show-current`, {
137
+ stdout: true,
138
+ silent: true,
139
+ disableLog: true,
140
+ }).trim();
141
+ if (options.copy) pbcopy(currentBranch);
142
+ else console.log(currentBranch);
143
+ return;
144
+ }
145
+
125
146
  if (options.changelog !== undefined || options.changelogBuild) {
126
- const ciIntegrationPrefix = 'ci(package-pwa-microservices-';
127
- const ciIntegrationVersionPrefix = 'New release v:';
128
147
  const releaseMatch = 'New release v:';
129
-
130
148
  // Helper: parse [<tag>] commits into grouped sections
131
149
  const buildSectionChangelog = (commits) => {
132
150
  const groups = {};
@@ -253,24 +271,13 @@ class UnderpostRepository {
253
271
  fs.writeFileSync(changelogPath, `# Changelog\n\n${changelog}`);
254
272
  logger.info('CHANGELOG.md built at', changelogPath);
255
273
  } else {
256
- // --changelog [latest-n]: print changelog of last N commits or since last release
274
+ // --changelog [latest-n]: print changelog of last N commits (default: 1)
257
275
  const hasExplicitCount =
258
276
  options.changelog !== undefined && options.changelog !== true && !isNaN(parseInt(options.changelog));
259
- const scanLimit = hasExplicitCount ? parseInt(options.changelog) : 500;
277
+ const scanLimit = hasExplicitCount ? parseInt(options.changelog) : 1;
260
278
  const allCommits = fetchHistory(scanLimit);
261
279
 
262
- let commits;
263
- if (!hasExplicitCount) {
264
- // No explicit count: find commits up to the last CI integration boundary
265
- const ciIndex = allCommits.findIndex(
266
- (c) => c.message.startsWith(ciIntegrationPrefix) && !c.message.match(ciIntegrationVersionPrefix),
267
- );
268
- commits = ciIndex >= 0 ? allCommits.slice(0, ciIndex) : allCommits;
269
- } else {
270
- commits = allCommits;
271
- }
272
-
273
- const sections = buildVersionSections(commits);
280
+ const sections = buildVersionSections(allCommits);
274
281
  let changelog = renderSections(sections);
275
282
  console.log(changelog || `No changelog entries found.\n`);
276
283
  }
@@ -360,7 +367,7 @@ class UnderpostRepository {
360
367
  * Pushes commits to a remote GitHub repository.
361
368
  * @param {string} [repoPath='./'] - The local path to the repository.
362
369
  * @param {string} [gitUri=`${process.env.GITHUB_USERNAME}/pwa-microservices-template`] - The URI of the GitHub repository.
363
- * @param {object} [options={ f: false, G8: false }] - Push options.
370
+ * @param {object} [options={ f: false, g8: false }] - Push options.
364
371
  * @param {boolean} [options.f=false] - If true, forces the push.
365
372
  * @param {boolean} [options.g8=false] - If true, uses the .g8 extension.
366
373
  * @memberof UnderpostRepository
@@ -368,9 +375,9 @@ class UnderpostRepository {
368
375
  push(
369
376
  repoPath = './',
370
377
  gitUri = `${process.env.GITHUB_USERNAME}/pwa-microservices-template`,
371
- options = { f: false, G8: false },
378
+ options = { f: false, g8: false },
372
379
  ) {
373
- const gExtension = options.G8 === true ? '.g8' : '.git';
380
+ const gExtension = options.g8 === true ? '.g8' : '.git';
374
381
  shellExec(
375
382
  `cd ${repoPath} && git push https://${process.env.GITHUB_TOKEN}@github.com/${gitUri}${gExtension}${
376
383
  options?.f === true ? ' --force' : ''
@@ -403,6 +410,7 @@ class UnderpostRepository {
403
410
  * @param {boolean} [options.cleanTemplate=false] - If true, cleans the pwa-microservices-template build directory.
404
411
  * @param {boolean} [options.build=false] - If true, builds the deployment to pwa-microservices-template (requires deployId).
405
412
  * @param {boolean} [options.syncConf=false] - If true, syncs configuration to private repositories (requires deployId).
413
+ * @param {boolean} [options.syncStart=false] - If true, syncs start scripts in deploy ID package.json with root package.json.
406
414
  * @param {boolean} [options.defaultConf=false] - If true, updates the default configuration file (requires deployId).
407
415
  * @param {string} [options.confWorkflowId=''] - If provided, uses this configuration workflow ID.
408
416
  * @returns {Promise<boolean>} A promise that resolves when the initialization is complete.
@@ -420,6 +428,7 @@ class UnderpostRepository {
420
428
  cleanTemplate: false,
421
429
  build: false,
422
430
  syncConf: false,
431
+ syncStart: false,
423
432
  defaultConf: false,
424
433
  confWorkflowId: '',
425
434
  },
@@ -448,6 +457,13 @@ class UnderpostRepository {
448
457
 
449
458
  if (options.deployId) {
450
459
  let deployId = options.deployId;
460
+
461
+ // Handle sync-start operation (before dd- prefix normalization to support 'dd' special case)
462
+ if (options.syncStart) {
463
+ shellExec(`node bin/deploy sync-start ${deployId}`);
464
+ return resolve(true);
465
+ }
466
+
451
467
  if (!deployId.startsWith('dd-')) deployId = `dd-${deployId}`;
452
468
  // Handle purge operation
453
469
  if (options.purge) {
@@ -547,7 +563,6 @@ class UnderpostRepository {
547
563
  const npmRoot = getNpmRootPath();
548
564
  const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
549
565
  const destFolder = `./${projectName}`;
550
- logger.info('Note: This process may take several minutes to complete');
551
566
  logger.info('build app', { destFolder });
552
567
  if (fs.existsSync(destFolder)) fs.removeSync(destFolder);
553
568
  fs.mkdirSync(destFolder, { recursive: true });
@@ -572,6 +587,209 @@ class UnderpostRepository {
572
587
  });
573
588
  },
574
589
 
590
+ /**
591
+ * Builds client assets, single replicas, and/or syncs environment ports.
592
+ * @param {string} [deployId='dd-default'] - The deployment ID.
593
+ * @param {string} [subConf=''] - The sub-configuration for the build.
594
+ * @param {string} [host=''] - Comma-separated hosts to filter the build.
595
+ * @param {string} [path=''] - Comma-separated paths to filter the build.
596
+ * @param {object} [options] - Build options.
597
+ * @param {boolean} [options.syncEnvPort=false] - If true, syncs environment port assignments across all deploy IDs.
598
+ * @param {boolean} [options.singleReplica=false] - If true, builds single replica folders instead of full client.
599
+ * @param {boolean} [options.buildZip=false] - If true, creates zip files of the builds.
600
+ * @param {boolean} [options.liteBuild=false] - If true, skips full build (default is full build).
601
+ * @param {boolean} [options.iconsBuild=false] - If true, builds icons.
602
+ * @returns {Promise<boolean>} A promise that resolves when the build is complete.
603
+ * @memberof UnderpostRepository
604
+ */
605
+ client(
606
+ deployId = 'dd-default',
607
+ subConf = '',
608
+ host = '',
609
+ path = '',
610
+ options = {
611
+ syncEnvPort: false,
612
+ singleReplica: false,
613
+ buildZip: false,
614
+ liteBuild: false,
615
+ iconsBuild: false,
616
+ },
617
+ ) {
618
+ return new Promise(async (resolve, reject) => {
619
+ try {
620
+ // Handle syncEnvPort operation
621
+ if (options.syncEnvPort) {
622
+ const dataDeploy = await getDataDeploy({ disableSyncEnvPort: true });
623
+ const dataEnv = [
624
+ { env: 'production', port: 3000 },
625
+ { env: 'development', port: 4000 },
626
+ { env: 'test', port: 5000 },
627
+ ];
628
+ let portOffset = 0;
629
+ const singleReplicaPortOffsets = {};
630
+ for (const deployIdObj of dataDeploy) {
631
+ const { deployId } = deployIdObj;
632
+ const baseConfPath = fs.existsSync(`./engine-private/replica/${deployId}`)
633
+ ? `./engine-private/replica`
634
+ : `./engine-private/conf`;
635
+
636
+ const effectivePortOffset =
637
+ singleReplicaPortOffsets[deployId] !== undefined ? singleReplicaPortOffsets[deployId] : portOffset;
638
+
639
+ for (const envInstanceObj of dataEnv) {
640
+ const envPath = `${baseConfPath}/${deployId}/.env.${envInstanceObj.env}`;
641
+ const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
642
+ envObj.PORT = `${envInstanceObj.port + effectivePortOffset}`;
643
+ writeEnv(envPath, envObj);
644
+ }
645
+
646
+ if (singleReplicaPortOffsets[deployId] !== undefined) continue;
647
+
648
+ const serverConf = loadReplicas(
649
+ deployId,
650
+ loadConfServerJson(`${baseConfPath}/${deployId}/conf.server.json`),
651
+ );
652
+ for (const host of Object.keys(serverConf)) {
653
+ let deferredSingleReplicaSlots = [];
654
+ for (const path of Object.keys(serverConf[host])) {
655
+ if (serverConf[host][path].singleReplica && serverConf[host][path].replicas) {
656
+ deferredSingleReplicaSlots.push({
657
+ replicas: serverConf[host][path].replicas,
658
+ peer: !!serverConf[host][path].peer,
659
+ });
660
+ continue;
661
+ }
662
+ portOffset++;
663
+ if (serverConf[host][path].peer) portOffset++;
664
+ }
665
+ for (const slot of deferredSingleReplicaSlots) {
666
+ for (const replica of slot.replicas) {
667
+ const replicaDeployId = buildReplicaId({ deployId, replica });
668
+ singleReplicaPortOffsets[replicaDeployId] = portOffset;
669
+ portOffset++;
670
+ if (slot.peer) portOffset++;
671
+ }
672
+ }
673
+ }
674
+ }
675
+ return resolve(true);
676
+ }
677
+
678
+ // Handle singleReplica operation
679
+ if (options.singleReplica) {
680
+ const replicaPath = path;
681
+ if (!deployId || !host || !replicaPath) {
682
+ logger.error('client --single-replica requires deploy-id, host, and path arguments');
683
+ return reject(false);
684
+ }
685
+ const serverConf = loadReplicas(
686
+ deployId,
687
+ loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
688
+ );
689
+
690
+ if (serverConf[host][replicaPath].replicas) {
691
+ {
692
+ let replicaIndex = -1;
693
+ for (const replica of serverConf[host][replicaPath].replicas) {
694
+ replicaIndex++;
695
+ const replicaDeployId = `${deployId}-${serverConf[host][replicaPath].replicas[replicaIndex].slice(1)}`;
696
+ await fs.copy(`./engine-private/conf/${deployId}`, `./engine-private/replica/${replicaDeployId}`);
697
+ fs.writeFileSync(
698
+ `./engine-private/replica/${replicaDeployId}/package.json`,
699
+ fs
700
+ .readFileSync(`./engine-private/replica/${replicaDeployId}/package.json`, 'utf8')
701
+ .replaceAll(`${deployId}`, `${replicaDeployId}`),
702
+ 'utf8',
703
+ );
704
+ const replicaFolder = `./engine-private/replica/${replicaDeployId}`;
705
+ for (const envFile of ['.env.production', '.env.development', '.env.test']) {
706
+ const envFilePath = `${replicaFolder}/${envFile}`;
707
+ if (fs.existsSync(envFilePath)) {
708
+ fs.writeFileSync(
709
+ envFilePath,
710
+ fs
711
+ .readFileSync(envFilePath, 'utf8')
712
+ .replaceAll(`DEPLOY_ID=${deployId}`, `DEPLOY_ID=${replicaDeployId}`),
713
+ 'utf8',
714
+ );
715
+ }
716
+ }
717
+ }
718
+ }
719
+ {
720
+ let replicaIndex = -1;
721
+ for (const replica of serverConf[host][replicaPath].replicas) {
722
+ replicaIndex++;
723
+ const replicaDeployId = `${deployId}-${serverConf[host][replicaPath].replicas[replicaIndex].slice(1)}`;
724
+ let replicaServerConf = JSON.parse(
725
+ fs.readFileSync(`./engine-private/replica/${replicaDeployId}/conf.server.json`, 'utf8'),
726
+ );
727
+
728
+ const singleReplicaConf = replicaServerConf[host][replicaPath];
729
+ singleReplicaConf.replicas = undefined;
730
+ singleReplicaConf.singleReplica = undefined;
731
+
732
+ replicaServerConf = {};
733
+ replicaServerConf[host] = {};
734
+ replicaServerConf[host][replica] = singleReplicaConf;
735
+
736
+ fs.writeFileSync(
737
+ `./engine-private/replica/${replicaDeployId}/conf.server.json`,
738
+ JSON.stringify(replicaServerConf, null, 4),
739
+ 'utf8',
740
+ );
741
+ }
742
+ }
743
+ }
744
+ return resolve(true);
745
+ }
746
+
747
+ // Handle buildFullClient operation (default)
748
+ {
749
+ const { deployId: resolvedDeployId } = loadConf(deployId, subConf ?? '');
750
+
751
+ let argHost = host ? host.split(',') : [];
752
+ let argPath = path ? path.split(',') : [];
753
+ let deployIdSingleReplicas = [];
754
+ let singleReplicaHosts = [];
755
+ const serverConf = resolvedDeployId
756
+ ? readConfJson(resolvedDeployId, 'server', { loadReplicas: true })
757
+ : Config.default.server;
758
+ const confFilePath = resolvedDeployId ? getConfFilePath(resolvedDeployId, 'server') : null;
759
+ const originalConfBackup = confFilePath ? fs.readFileSync(confFilePath, 'utf8') : null;
760
+ for (const host of Object.keys(serverConf)) {
761
+ for (const path of Object.keys(serverConf[host])) {
762
+ if (argHost.length && argPath.length && (!argHost.includes(host) || !argPath.includes(path))) {
763
+ delete serverConf[host][path];
764
+ } else {
765
+ if (serverConf[host][path].singleReplica && serverConf[host][path].replicas) {
766
+ singleReplicaHosts.push({ host, path });
767
+ deployIdSingleReplicas = deployIdSingleReplicas.concat(
768
+ serverConf[host][path].replicas.map((replica) =>
769
+ buildReplicaId({ deployId: resolvedDeployId, replica }),
770
+ ),
771
+ );
772
+ }
773
+ }
774
+ }
775
+ }
776
+ await buildClient({
777
+ buildZip: options.buildZip || false,
778
+ fullBuild: options.liteBuild ? false : true,
779
+ iconsBuild: options.iconsBuild || false,
780
+ });
781
+ for (const replicaDeployId of deployIdSingleReplicas) await Underpost.repo.client(replicaDeployId);
782
+
783
+ return resolve(true);
784
+ }
785
+ } catch (error) {
786
+ console.log(error);
787
+ logger.error(error, error.stack);
788
+ return reject(false);
789
+ }
790
+ });
791
+ },
792
+
575
793
  /**
576
794
  * Gets a list of deleted files from a Git repository.
577
795
  * @param {string} [path='.'] - The path to the repository.
@@ -720,25 +938,14 @@ Prevent build private config repo.`,
720
938
  DefaultConf.client = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.client.json`, 'utf8'));
721
939
  DefaultConf.server = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
722
940
  DefaultConf.ssr = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.ssr.json`, 'utf8'));
723
- // DefaultConf.cron = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.cron.json`, 'utf8'));
724
-
725
- for (const host of Object.keys(DefaultConf.server)) {
726
- for (const path of Object.keys(DefaultConf.server[host])) {
727
- DefaultConf.server[host][path].db = defaultServer.db;
728
- DefaultConf.server[host][path].mailer = defaultServer.mailer;
729
-
730
- delete DefaultConf.server[host][path]._wp_client;
731
- delete DefaultConf.server[host][path]._wp_git;
732
- delete DefaultConf.server[host][path]._wp_directory;
733
- delete DefaultConf.server[host][path].wp;
734
- delete DefaultConf.server[host][path].git;
735
- delete DefaultConf.server[host][path].directory;
736
- }
737
- }
738
941
  } else
739
942
  logger.warn(
740
943
  `Deploy ID configuration not found: ./engine-private/conf/${deployId}, using default configuration.`,
741
944
  );
945
+
946
+ // Serialize the configuration into the conf.*.js manifest file.
947
+ // env: references from JSON configs are preserved as 'env:KEY' strings.
948
+ // At runtime, resolveConfSecrets() in conf.js resolves them via process.env.
742
949
  const sepRender = '/**/';
743
950
  const confRawPaths = fs.readFileSync('./conf.js', 'utf8').split(sepRender);
744
951
  confRawPaths[1] = `${JSON.stringify(DefaultConf)};`;
@@ -924,6 +1131,82 @@ Prevent build private config repo.`,
924
1131
 
925
1132
  return copiedFiles;
926
1133
  },
1134
+
1135
+ /**
1136
+ * Dispatches a GitHub Actions workflow using gh CLI or curl fallback.
1137
+ * @param {object} options - Dispatch options.
1138
+ * @param {string} options.repo - The GitHub repository (e.g., "owner/repo").
1139
+ * @param {string} options.workflowFile - The workflow file name (e.g., "engine-core.cd.yml").
1140
+ * @param {string} [options.ref='master'] - The git ref to dispatch against.
1141
+ * @param {object} [options.inputs={}] - Key-value inputs for the workflow_dispatch event.
1142
+ * @memberof UnderpostRepository
1143
+ */
1144
+ dispatchWorkflow(options = { repo: '', workflowFile: '', ref: 'master', inputs: {} }) {
1145
+ const { repo, workflowFile, ref, inputs } = options;
1146
+ const ghAvailable = shellExec('command -v gh 2>/dev/null', {
1147
+ stdout: true,
1148
+ silent: true,
1149
+ disableLog: true,
1150
+ }).trim();
1151
+
1152
+ if (ghAvailable) {
1153
+ let cmd = `gh workflow run ${workflowFile} --repo ${repo} --ref ${ref}`;
1154
+ for (const [key, value] of Object.entries(inputs)) {
1155
+ if (value !== undefined && value !== '') {
1156
+ const escaped = String(value).replace(/'/g, "'\\''");
1157
+ cmd += ` -f ${key}='${escaped}'`;
1158
+ }
1159
+ }
1160
+ shellExec(cmd);
1161
+ } else {
1162
+ let token = process.env.GITHUB_TOKEN;
1163
+ if (!token) {
1164
+ const envPath = `${getNpmRootPath()}/underpost/.env`;
1165
+ if (fs.existsSync(envPath) && fs.statSync(envPath).isFile()) {
1166
+ const envVars = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
1167
+ token = envVars.GITHUB_TOKEN;
1168
+ }
1169
+ }
1170
+ if (!token) {
1171
+ logger.error('GITHUB_TOKEN is required for workflow dispatch (gh CLI not available)');
1172
+ return;
1173
+ }
1174
+ const payload = { ref };
1175
+ if (Object.keys(inputs).length > 0) payload.inputs = inputs;
1176
+ const payloadJson = JSON.stringify(payload).replace(/'/g, "'\\''");
1177
+ shellExec(
1178
+ `curl -s -f -X POST ` +
1179
+ `-H "Accept: application/vnd.github.v3+json" ` +
1180
+ `-H "Authorization: token ${token}" ` +
1181
+ `"https://api.github.com/repos/${repo}/actions/workflows/${workflowFile}/dispatches" ` +
1182
+ `-d '${payloadJson}'`,
1183
+ );
1184
+ }
1185
+ logger.info('Dispatched workflow', `${repo} -> ${workflowFile}`, inputs.job ? `(job: ${inputs.job})` : '');
1186
+ },
1187
+
1188
+ /**
1189
+ * Sanitizes a markdown changelog string into a compact message format.
1190
+ * Strips date headers, converts section tags to `[tag]` prefixes, removes bullet markers and special characters.
1191
+ * @param {string} message - The raw markdown changelog output.
1192
+ * @returns {string} The sanitized single-line or multi-line compact message.
1193
+ * @memberof UnderpostRepository
1194
+ */
1195
+ sanitizeChangelogMessage(message) {
1196
+ if (!message) return '';
1197
+ return message
1198
+ .replace(/^##\s+\d{4}-\d{2}-\d{2}\s*/gm, '')
1199
+ .replace(/^###\s+(\S+)\s*/gm, '[$1] ')
1200
+ .replace(/^- /gm, '')
1201
+ .replaceAll('"', '')
1202
+ .replaceAll('`', '')
1203
+ .split('\n')
1204
+ .map((l) => l.trim())
1205
+ .filter(Boolean)
1206
+ .join('\n')
1207
+ .trim()
1208
+ .replaceAll('] - ', '] ');
1209
+ },
927
1210
  };
928
1211
  }
929
1212