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
@@ -8,6 +8,7 @@ import dotenv from 'dotenv';
8
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
+ import path from 'path';
11
12
  import fs from 'fs-extra';
12
13
  import {
13
14
  getNpmRootPath,
@@ -133,10 +134,21 @@ class UnderpostRepository {
133
134
  p: undefined,
134
135
  bc: '',
135
136
  isRemoteRepo: '',
137
+ hasChanges: false,
136
138
  },
137
139
  ) {
138
140
  if (!repoPath) repoPath = '.';
139
141
 
142
+ if (options.hasChanges) {
143
+ const status = shellExec(`cd ${repoPath} && git status --porcelain`, {
144
+ stdout: true,
145
+ silent: true,
146
+ disableLog: true,
147
+ }).trim();
148
+ process.stdout.write(status ? '1' : '');
149
+ return;
150
+ }
151
+
140
152
  if (options.isRemoteRepo) {
141
153
  const accessible = Underpost.repo.isRemoteRepo(options.isRemoteRepo);
142
154
  console.log(accessible);
@@ -556,7 +568,7 @@ class UnderpostRepository {
556
568
  // Handle sync-conf operation
557
569
  if (options.syncConf) {
558
570
  logger.info(`Syncing configuration for deploy ID: ${deployId}`);
559
- shellExec(`node bin/build ${deployId} conf`);
571
+ shellExec(`node bin/build ${deployId} --conf`);
560
572
  logger.info('Configuration synced successfully');
561
573
  return resolve(true);
562
574
  }
@@ -608,7 +620,8 @@ class UnderpostRepository {
608
620
  const npmRoot = getNpmRootPath();
609
621
  const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
610
622
  const destFolder = `./${projectName}`;
611
- logger.info('build app', { destFolder });
623
+ const deployId = projectName.startsWith('dd-') ? projectName : `dd-${projectName}`;
624
+ logger.info('build app', { destFolder, deployId });
612
625
  if (fs.existsSync(destFolder)) fs.removeSync(destFolder);
613
626
  fs.mkdirSync(destFolder, { recursive: true });
614
627
  if (!options.dev) {
@@ -621,8 +634,9 @@ class UnderpostRepository {
621
634
  UnderpostRepository.API.initLocalRepo({ path: destFolder });
622
635
  shellExec(`cd ${destFolder} && git add . && git commit -m "Base template implementation"`);
623
636
  }
624
- shellExec(`cd ${destFolder} && npm run build`);
625
- shellExec(`cd ${destFolder} && npm run dev`);
637
+ shellExec(`cd ${destFolder} && node bin new --deploy-id ${deployId} --default-conf`);
638
+ shellExec(`cd ${destFolder} && node bin client ${deployId}`);
639
+ shellExec(`cd ${destFolder} && DEPLOY_ID=${deployId} npm run dev`);
626
640
  }
627
641
  return resolve(true);
628
642
  } catch (error) {
@@ -852,6 +866,7 @@ class UnderpostRepository {
852
866
  }
853
867
  }
854
868
  await buildClient({
869
+ deployId: resolvedDeployId,
855
870
  buildZip: options.buildZip || false,
856
871
  split: options.split || '',
857
872
  fullBuild: options.liteBuild ? false : true,
@@ -862,7 +877,12 @@ class UnderpostRepository {
862
877
  logger.warn('Skip replica client build: replica folder not found', { replicaDeployId });
863
878
  continue;
864
879
  }
865
- await Underpost.repo.client(replicaDeployId);
880
+ await Underpost.repo.client(replicaDeployId, '', '', '', {
881
+ buildZip: options.buildZip || false,
882
+ split: options.split || '',
883
+ liteBuild: options.liteBuild || false,
884
+ iconsBuild: options.iconsBuild || false,
885
+ });
866
886
  }
867
887
 
868
888
  return resolve(true);
@@ -924,8 +944,6 @@ class UnderpostRepository {
924
944
  shellExec(`cd ${privateRepoPath} && underpost pull . ${process.env.GITHUB_USERNAME}/${privateRepoName}`, {
925
945
  silent: true,
926
946
  });
927
- shellExec(`underpost run secret`);
928
- shellExec(`underpost run underpost-config`);
929
947
  const packageJsonDeploy = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/package.json`, 'utf8'));
930
948
  const packageJsonEngine = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
931
949
  if (packageJsonDeploy.version !== packageJsonEngine.version) {
@@ -939,7 +957,7 @@ Prevent build private config repo.`,
939
957
  deployVersion: packageJsonDeploy.version,
940
958
  };
941
959
  }
942
- shellExec(`node bin/build ${deployId} conf`);
960
+ shellExec(`node bin/build ${deployId} --conf`);
943
961
  return {
944
962
  validVersion: true,
945
963
  engineVersion: packageJsonEngine.version,
@@ -1008,11 +1026,14 @@ Prevent build private config repo.`,
1008
1026
  const host = 'default.net';
1009
1027
  const path = '/';
1010
1028
  DefaultConf.server[host][path].valkey = {
1011
- port: 6379,
1012
- host: 'valkey-service.default.svc.cluster.local',
1029
+ port: 'env:VALKEY_PORT:int:6379',
1030
+ host: 'env:VALKEY_HOST:127.0.0.1',
1013
1031
  };
1014
- // mongodb-0.mongodb-service
1015
- DefaultConf.server[host][path].db.host = 'mongodb://mongodb-service:27017';
1032
+ DefaultConf.server[host][path].db.host = 'env:DB_HOST:mongodb://127.0.0.1:27017';
1033
+ DefaultConf.server[host][path].db.replicaSet = 'env:DB_REPLICA_SET:rs0';
1034
+ DefaultConf.server[host][path].db.authSource = 'env:DB_AUTH_SOURCE:admin';
1035
+ DefaultConf.server[host][path].db.user = 'env:DB_USER:';
1036
+ DefaultConf.server[host][path].db.password = 'env:DB_PASSWORD:';
1016
1037
  defaultConf = true;
1017
1038
  break;
1018
1039
  }
@@ -1048,9 +1069,9 @@ Prevent build private config repo.`,
1048
1069
  */
1049
1070
  clean(options = { paths: [''] }) {
1050
1071
  for (const path of options.paths) {
1051
- shellExec(`cd ${path} && git reset`, { silent: true });
1052
- shellExec(`cd ${path} && git checkout .`, { silent: true });
1053
- shellExec(`cd ${path} && git clean -f -d`, { silent: true });
1072
+ shellExec(`cd ${path} && git reset`, { silentOnError: true, silent: true, disableLog: true });
1073
+ shellExec(`cd ${path} && git checkout .`, { silentOnError: true, silent: true, disableLog: true });
1074
+ shellExec(`cd ${path} && git clean -f -d`, { silentOnError: true, silent: true, disableLog: true });
1054
1075
  }
1055
1076
  },
1056
1077
 
@@ -1104,7 +1125,7 @@ Prevent build private config repo.`,
1104
1125
 
1105
1126
  try {
1106
1127
  // Fetch directory contents recursively
1107
- const copiedFiles = await this._fetchAndCopyGitHubDirectory({
1128
+ const copiedFiles = await this.fetchAndCopyGitHubDirectory({
1108
1129
  apiUrl,
1109
1130
  targetPath,
1110
1131
  basePath: directoryPath,
@@ -1140,7 +1161,7 @@ Prevent build private config repo.`,
1140
1161
  * @returns {Promise<array>} Array of copied file paths.
1141
1162
  * @memberof UnderpostRepository
1142
1163
  */
1143
- async _fetchAndCopyGitHubDirectory(options) {
1164
+ async fetchAndCopyGitHubDirectory(options) {
1144
1165
  const { apiUrl, targetPath, basePath, branch } = options;
1145
1166
  const copiedFiles = [];
1146
1167
 
@@ -1175,14 +1196,12 @@ Prevent build private config repo.`,
1175
1196
 
1176
1197
  logger.info(`Found ${contents.length} items in directory: ${basePath}`);
1177
1198
 
1178
- // Process each item in the directory
1179
1199
  for (const item of contents) {
1180
1200
  const itemTargetPath = `${targetPath}/${item.name}`;
1181
1201
 
1182
1202
  if (item.type === 'file') {
1183
1203
  logger.info(`Downloading file: ${item.path}`);
1184
1204
 
1185
- // Download file content
1186
1205
  const fileResponse = await fetch(item.download_url);
1187
1206
  if (!fileResponse.ok) {
1188
1207
  logger.error(`Failed to download: ${item.download_url}`);
@@ -1192,16 +1211,14 @@ Prevent build private config repo.`,
1192
1211
  const fileContent = await fileResponse.text();
1193
1212
  fs.writeFileSync(itemTargetPath, fileContent);
1194
1213
 
1195
- logger.info(`✓ Saved: ${itemTargetPath}`);
1214
+ logger.info(`Saved: ${itemTargetPath}`);
1196
1215
  copiedFiles.push(itemTargetPath);
1197
1216
  } else if (item.type === 'dir') {
1198
- logger.info(`📁 Processing directory: ${item.path}`);
1217
+ logger.info(`Processing directory: ${item.path}`);
1199
1218
 
1200
- // Create subdirectory
1201
1219
  fs.mkdirSync(itemTargetPath, { recursive: true });
1202
1220
 
1203
- // Recursively process subdirectory
1204
- const subFiles = await this._fetchAndCopyGitHubDirectory({
1221
+ const subFiles = await this.fetchAndCopyGitHubDirectory({
1205
1222
  apiUrl: item.url,
1206
1223
  targetPath: itemTargetPath,
1207
1224
  basePath: item.path,
@@ -1209,7 +1226,7 @@ Prevent build private config repo.`,
1209
1226
  });
1210
1227
 
1211
1228
  copiedFiles.push(...subFiles);
1212
- logger.info(`✓ Completed directory: ${item.path} (${subFiles.length} files)`);
1229
+ logger.info(`Completed directory: ${item.path} (${subFiles.length} files)`);
1213
1230
  } else {
1214
1231
  logger.warn(`Skipping unknown item type '${item.type}': ${item.path}`);
1215
1232
  }
@@ -1295,7 +1312,7 @@ Prevent build private config repo.`,
1295
1312
  },
1296
1313
  /**
1297
1314
  * Checks whether a remote Git repository URL is reachable.
1298
- * Uses `git ls-remote` with `|| true` so the process always exits 0.
1315
+ * Uses `silentOnError` so a non-reachable remote returns false instead of throwing.
1299
1316
  * Injects `GITHUB_TOKEN` into GitHub HTTPS URLs when available.
1300
1317
  * @param {string} url - Full HTTPS clone URL to test (e.g. "https://github.com/org/repo.git").
1301
1318
  * @returns {boolean} `true` when the remote responded with at least one ref hash.
@@ -1305,10 +1322,11 @@ Prevent build private config repo.`,
1305
1322
  if (!url) return false;
1306
1323
  const authUrl = Underpost.repo.resolveAuthUrl(url);
1307
1324
  // GIT_TERMINAL_PROMPT=0 prevents git from hanging on credential prompts inside containers.
1308
- const raw = shellExec(`GIT_TERMINAL_PROMPT=0 git ls-remote "${authUrl}" HEAD 2>&1 || true`, {
1325
+ const raw = shellExec(`GIT_TERMINAL_PROMPT=0 git ls-remote "${authUrl}" HEAD 2>&1`, {
1309
1326
  stdout: true,
1310
1327
  silent: true,
1311
1328
  disableLog: true,
1329
+ silentOnError: true,
1312
1330
  });
1313
1331
  logger.info('isRemoteRepo', { url, raw: (raw || '').slice(0, 120) });
1314
1332
  return typeof raw === 'string' && /^[0-9a-f]{40}\t/m.test(raw);
@@ -1376,15 +1394,17 @@ Prevent build private config repo.`,
1376
1394
  const gitEmail = process.env.GITHUB_EMAIL || `development@underpost.net`;
1377
1395
 
1378
1396
  if (!fs.existsSync(`${repoPath}/.git`)) {
1379
- shellExec(`cd "${repoPath}" && git init`);
1397
+ shellExec(`mkdir -p "${repoPath}" && git init "${repoPath}"`);
1380
1398
  }
1399
+
1381
1400
  shellExec(`cd "${repoPath}" && git config user.name '${gitUsername}'`);
1382
1401
  shellExec(`cd "${repoPath}" && git config user.email '${gitEmail}'`);
1383
-
1402
+ shellExec(`cd "${repoPath}" && git config core.filemode false`);
1384
1403
  if (origin) {
1385
- const currentRemote = shellExec(`cd "${repoPath}" && git remote get-url origin 2>/dev/null || true`, {
1404
+ const currentRemote = shellExec(`cd "${repoPath}" && git remote get-url origin`, {
1386
1405
  stdout: true,
1387
1406
  silent: true,
1407
+ silentOnError: true,
1388
1408
  }).trim();
1389
1409
  if (!currentRemote) {
1390
1410
  shellExec(`cd "${repoPath}" && git remote add origin "${origin}"`);
@@ -1608,6 +1628,125 @@ Prevent build private config repo.`,
1608
1628
  logger.info('engine-private in /home/dd removed');
1609
1629
  }
1610
1630
  },
1631
+
1632
+ /**
1633
+ * Resolves the GitHub repository for a given instance runtime by scanning
1634
+ * every `conf.instances.json` listed in `./engine-private/deploy/dd.router`.
1635
+ *
1636
+ * Resolution order:
1637
+ * 1. If `runtime` is falsy, returns `${GITHUB_USERNAME}/engine`.
1638
+ * 2. Iterates each deploy ID found in `dd.router` and looks for an instance
1639
+ * whose `runtime` field matches the supplied value.
1640
+ * 3. When a match is found, returns `instance.metadata.repository`.
1641
+ * 4. Falls back to `${GITHUB_USERNAME}/engine` when no match is found.
1642
+ *
1643
+ * @param {string} [runtime=''] - The runtime identifier to look up (e.g. `'cyberia-server'`, `'cyberia-client'`).
1644
+ * @returns {string} The resolved `owner/repo` string.
1645
+ * @memberof UnderpostRepository
1646
+ */
1647
+ resolveInstanceRepo(runtime = '') {
1648
+ const fallback = `${process.env.GITHUB_USERNAME}/engine`;
1649
+ if (!runtime) return fallback;
1650
+ const ddRouter = './engine-private/deploy/dd.router';
1651
+ const deployIds = fs.existsSync(ddRouter)
1652
+ ? fs
1653
+ .readFileSync(ddRouter, 'utf8')
1654
+ .split(',')
1655
+ .map((s) => s.trim())
1656
+ .filter(Boolean)
1657
+ : [];
1658
+ for (const deployId of deployIds) {
1659
+ const confPath = `./engine-private/conf/${deployId}/conf.instances.json`;
1660
+ if (!fs.existsSync(confPath)) continue;
1661
+ try {
1662
+ const instances = JSON.parse(fs.readFileSync(confPath, 'utf8'));
1663
+ const match = instances.find((i) => i && i.runtime === runtime);
1664
+ if (match && match.metadata && match.metadata.repository) {
1665
+ logger.info(`[resolveInstanceRepo] resolved from ${confPath}`, {
1666
+ runtime,
1667
+ repo: match.metadata.repository,
1668
+ });
1669
+ return match.metadata.repository;
1670
+ }
1671
+ } catch (err) {
1672
+ logger.warn(`[resolveInstanceRepo] failed to parse ${confPath}: ${err.message}`);
1673
+ }
1674
+ }
1675
+ return fallback;
1676
+ },
1677
+
1678
+ /**
1679
+ * Performs a shallow sparse Git checkout of a single subdirectory from any
1680
+ * GitHub repository into a local target directory.
1681
+ *
1682
+ * Uses `--depth 1 --no-checkout` + `git sparse-checkout` so only the
1683
+ * requested path is fetched — no full clone of the remote repo.
1684
+ * Skips the clone entirely when `<targetDir>/<subPath>` already exists on
1685
+ * disk (idempotent).
1686
+ *
1687
+ * Requires `GITHUB_TOKEN` to be set in the environment for authenticated
1688
+ * access to private repositories.
1689
+ *
1690
+ * @param {string} subPath - The subdirectory path within the remote repo to
1691
+ * check out (e.g. `'conf/dd-prototype'`, `'src/api/payments'`).
1692
+ * @param {object} [options]
1693
+ * @param {string} [options.repoOwner='underpostnet'] - GitHub organisation or
1694
+ * user that owns the repository.
1695
+ * @param {string} [options.repoName='engine-private'] - Name of the
1696
+ * repository on GitHub.
1697
+ * @param {string} [options.targetDir='./engine-private'] - Local directory
1698
+ * where the repo will be cloned.
1699
+ * @returns {boolean} `true` when the checkout was performed, `false` when it
1700
+ * was skipped because the target path already existed.
1701
+ * @memberof UnderpostRepository
1702
+ */
1703
+ sparseCheckoutDirectory(
1704
+ subPath,
1705
+ options = { repoOwner: 'underpostnet', repoName: 'engine-private', targetDir: './engine-private' },
1706
+ ) {
1707
+ const { repoOwner = 'underpostnet', repoName = 'engine-private', targetDir = './engine-private' } = options;
1708
+ const localPath = `${targetDir}/${subPath}`;
1709
+ if (fs.existsSync(localPath)) {
1710
+ logger.info('[sparseCheckoutDirectory] path already present, skipping', localPath);
1711
+ return false;
1712
+ }
1713
+ const authUrl = `https://${process.env.GITHUB_TOKEN}@github.com/${repoOwner}/${repoName}.git`;
1714
+ shellExec(`git clone --depth 1 --no-checkout ${authUrl} ${targetDir}`, { disableLog: true });
1715
+ shellExec(`cd ${targetDir} && git sparse-checkout set ${subPath} && git checkout`, { disableLog: true });
1716
+ logger.info('[sparseCheckoutDirectory] sparse checkout complete', localPath);
1717
+ return true;
1718
+ },
1719
+
1720
+ /**
1721
+ * Ensures a deploy's public source repo (e.g. `engine-prototype`) is present
1722
+ * next to the engine and reset to a pristine HEAD, so catalog `sourceMoves`
1723
+ * can (re)pull custom sources even after a previous build moved them out of
1724
+ * the source tree.
1725
+ *
1726
+ * Clones `../<repoName>` when missing; otherwise restores a clean checkout
1727
+ * (`git checkout .` brings back any moved-out tracked files) and pulls latest.
1728
+ * Mirrors the sibling-repo handling used by `syncPrivateConf`.
1729
+ *
1730
+ * @param {string} repoName - Public source repo name (e.g. `engine-prototype`).
1731
+ * @returns {boolean} `true` when the repo is available on disk.
1732
+ * @memberof UnderpostRepository
1733
+ */
1734
+ pullSourceRepo(repoName) {
1735
+ const username = process.env.GITHUB_USERNAME;
1736
+ if (!username || !repoName) return false;
1737
+ const repoPath = `../${repoName}`;
1738
+ const gitUri = `${username}/${repoName}`;
1739
+ if (!fs.existsSync(repoPath)) {
1740
+ shellExec(`cd .. && underpost clone ${gitUri}`, { silent: true });
1741
+ } else {
1742
+ const repoAbsPath = path.resolve(repoPath);
1743
+ shellExec(`git config --global --add safe.directory '${repoAbsPath}'`);
1744
+ shellExec(`cd ${repoPath} && git checkout . && git clean -f -d && underpost pull . ${gitUri}`, {
1745
+ silent: true,
1746
+ });
1747
+ }
1748
+ return fs.existsSync(repoPath);
1749
+ },
1611
1750
  };
1612
1751
  }
1613
1752