cyberia 3.2.5 → 3.2.9

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 (301) hide show
  1. package/.github/workflows/engine-cyberia.cd.yml +2 -2
  2. package/.github/workflows/release.cd.yml +1 -2
  3. package/CHANGELOG.md +351 -1
  4. package/CLI-HELP.md +40 -13
  5. package/Dockerfile +0 -4
  6. package/README.md +242 -497
  7. package/bin/build.js +19 -5
  8. package/bin/cyberia.js +1149 -240
  9. package/bin/deploy.js +570 -1
  10. package/bin/file.js +6 -0
  11. package/bin/index.js +1149 -240
  12. package/bin/vs.js +1 -1
  13. package/conf.js +67 -89
  14. package/deployment.yaml +4 -222
  15. package/hardhat/package-lock.json +32 -32
  16. package/hardhat/package.json +3 -3
  17. package/jsconfig.json +1 -1
  18. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  19. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
  20. package/manifests/deployment/dd-cyberia-development/deployment.yaml +4 -222
  21. package/manifests/deployment/dd-cyberia-development/proxy.yaml +10 -118
  22. package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
  23. package/manifests/deployment/dd-test-development/deployment.yaml +136 -66
  24. package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
  25. package/package.json +23 -14
  26. package/proxy.yaml +10 -118
  27. package/scripts/k3s-node-setup.sh +2 -2
  28. package/scripts/nat-iptables.sh +103 -18
  29. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +18 -18
  30. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +7 -14
  31. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +76 -21
  32. package/src/api/core/core.controller.js +10 -10
  33. package/src/api/core/core.service.js +10 -10
  34. package/src/api/crypto/crypto.controller.js +8 -8
  35. package/src/api/crypto/crypto.service.js +8 -8
  36. package/src/api/cyberia-action/cyberia-action.controller.js +74 -0
  37. package/src/api/cyberia-action/cyberia-action.model.js +87 -0
  38. package/src/api/cyberia-action/cyberia-action.router.js +27 -0
  39. package/src/api/cyberia-action/cyberia-action.service.js +42 -0
  40. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +13 -13
  41. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +11 -11
  42. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
  43. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +16 -16
  44. package/src/api/cyberia-entity/cyberia-entity.controller.js +10 -10
  45. package/src/api/cyberia-entity/cyberia-entity.service.js +10 -10
  46. package/src/api/cyberia-instance/cyberia-fallback-world.js +19 -209
  47. package/src/api/cyberia-instance/cyberia-instance.controller.js +14 -14
  48. package/src/api/cyberia-instance/cyberia-instance.model.js +3 -0
  49. package/src/api/cyberia-instance/cyberia-instance.service.js +22 -57
  50. package/src/api/cyberia-instance/cyberia-portal-connector.js +20 -246
  51. package/src/api/cyberia-instance/cyberia-world-generator.js +505 -0
  52. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +10 -10
  53. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +216 -55
  54. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +4 -1
  55. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +18 -14
  56. package/src/api/cyberia-map/cyberia-map.controller.js +10 -10
  57. package/src/api/cyberia-map/cyberia-map.service.js +10 -10
  58. package/src/api/cyberia-quest/cyberia-quest.controller.js +74 -0
  59. package/src/api/cyberia-quest/cyberia-quest.model.js +67 -0
  60. package/src/api/cyberia-quest/cyberia-quest.router.js +27 -0
  61. package/src/api/cyberia-quest/cyberia-quest.service.js +42 -0
  62. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +74 -0
  63. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +49 -0
  64. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +27 -0
  65. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +42 -0
  66. package/src/api/default/default.controller.js +10 -10
  67. package/src/api/default/default.service.js +10 -10
  68. package/src/api/document/document.controller.js +12 -12
  69. package/src/api/document/document.model.js +10 -16
  70. package/src/api/file/file.controller.js +8 -8
  71. package/src/api/file/file.model.js +10 -10
  72. package/src/api/file/file.service.js +36 -36
  73. package/src/api/instance/instance.controller.js +10 -10
  74. package/src/api/instance/instance.model.js +4 -10
  75. package/src/api/instance/instance.service.js +10 -10
  76. package/src/api/ipfs/ipfs.controller.js +12 -12
  77. package/src/api/ipfs/ipfs.model.js +4 -13
  78. package/src/api/ipfs/ipfs.service.js +14 -28
  79. package/src/api/object-layer/object-layer.controller.js +12 -12
  80. package/src/api/object-layer/object-layer.model.js +4 -17
  81. package/src/api/object-layer/object-layer.service.js +12 -12
  82. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +10 -10
  83. package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +6 -16
  84. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +18 -14
  85. package/src/api/test/test.controller.js +8 -8
  86. package/src/api/test/test.service.js +8 -8
  87. package/src/api/user/guest.service.js +99 -0
  88. package/src/api/user/user.controller.js +6 -6
  89. package/src/api/user/user.model.js +8 -13
  90. package/src/api/user/user.service.js +3 -20
  91. package/src/cli/cluster.js +61 -14
  92. package/src/cli/db.js +47 -2
  93. package/src/cli/deploy.js +67 -35
  94. package/src/cli/fs.js +79 -8
  95. package/src/cli/image.js +43 -1
  96. package/src/cli/index.js +26 -1
  97. package/src/cli/release.js +57 -1
  98. package/src/cli/repository.js +69 -31
  99. package/src/cli/run.js +415 -36
  100. package/src/cli/ssh.js +1 -1
  101. package/src/cli/static.js +43 -115
  102. package/src/client/Cryptokoyn.index.js +18 -21
  103. package/src/client/CyberiaPortal.index.js +19 -23
  104. package/src/client/Default.index.js +21 -33
  105. package/src/client/Itemledger.index.js +20 -26
  106. package/src/client/Underpost.index.js +19 -23
  107. package/src/client/components/core/404.js +4 -4
  108. package/src/client/components/core/500.js +4 -4
  109. package/src/client/components/core/Account.js +73 -60
  110. package/src/client/components/core/AgGrid.js +23 -33
  111. package/src/client/components/core/Alert.js +12 -13
  112. package/src/client/components/core/AppStore.js +1 -1
  113. package/src/client/components/core/Auth.js +35 -37
  114. package/src/client/components/core/Badge.js +7 -13
  115. package/src/client/components/core/BtnIcon.js +15 -17
  116. package/src/client/components/core/CalendarCore.js +42 -63
  117. package/src/client/components/core/Chat.js +13 -15
  118. package/src/client/components/core/ClientEvents.js +87 -0
  119. package/src/client/components/core/ColorPaletteElement.js +309 -0
  120. package/src/client/components/core/Content.js +17 -14
  121. package/src/client/components/core/Css.js +15 -71
  122. package/src/client/components/core/CssCore.js +12 -16
  123. package/src/client/components/core/D3Chart.js +4 -4
  124. package/src/client/components/core/Docs.js +64 -91
  125. package/src/client/components/core/DropDown.js +69 -91
  126. package/src/client/components/core/EventBus.js +92 -0
  127. package/src/client/components/core/EventsUI.js +14 -17
  128. package/src/client/components/core/FileExplorer.js +96 -228
  129. package/src/client/components/core/FullScreen.js +47 -75
  130. package/src/client/components/core/Input.js +24 -69
  131. package/src/client/components/core/Keyboard.js +25 -18
  132. package/src/client/components/core/KeyboardAvoidance.js +145 -0
  133. package/src/client/components/core/LoadingAnimation.js +25 -31
  134. package/src/client/components/core/LogIn.js +41 -41
  135. package/src/client/components/core/LogOut.js +23 -14
  136. package/src/client/components/core/Modal.js +462 -178
  137. package/src/client/components/core/NotificationManager.js +14 -18
  138. package/src/client/components/core/Panel.js +54 -50
  139. package/src/client/components/core/PanelForm.js +25 -125
  140. package/src/client/components/core/Polyhedron.js +110 -214
  141. package/src/client/components/core/PublicProfile.js +39 -32
  142. package/src/client/components/core/Recover.js +48 -44
  143. package/src/client/components/core/Responsive.js +88 -32
  144. package/src/client/components/core/RichText.js +9 -18
  145. package/src/client/components/core/Router.js +24 -3
  146. package/src/client/components/core/SearchBox.js +37 -37
  147. package/src/client/components/core/SignUp.js +39 -30
  148. package/src/client/components/core/SocketIo.js +31 -2
  149. package/src/client/components/core/SocketIoHandler.js +6 -6
  150. package/src/client/components/core/ToggleSwitch.js +8 -20
  151. package/src/client/components/core/ToolTip.js +5 -17
  152. package/src/client/components/core/Translate.js +56 -59
  153. package/src/client/components/core/Validator.js +26 -16
  154. package/src/client/components/core/Wallet.js +15 -26
  155. package/src/client/components/core/Worker.js +163 -27
  156. package/src/client/components/core/windowGetDimensions.js +7 -7
  157. package/src/client/components/cryptokoyn/{MenuCryptokoyn.js → AppShellCryptokoyn.js} +57 -57
  158. package/src/client/components/cryptokoyn/CssCryptokoyn.js +15 -15
  159. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +6 -4
  160. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +6 -4
  161. package/src/client/components/cryptokoyn/RouterCryptokoyn.js +37 -0
  162. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +4 -4
  163. package/src/client/components/cryptokoyn/SignUpCryptokoyn.js +6 -4
  164. package/src/client/components/cyberia/InstanceEngineCyberia.js +141 -60
  165. package/src/client/components/cyberia/MapEngineCyberia.js +691 -214
  166. package/src/client/components/cyberia/ObjectLayerEngine.js +19 -0
  167. package/src/client/components/cyberia/ObjectLayerEngineModal.js +1204 -94
  168. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +196 -298
  169. package/src/client/components/cyberia-portal/{MenuCyberiaPortal.js → AppShellCyberiaPortal.js} +102 -102
  170. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +305 -61
  171. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +15 -15
  172. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +6 -4
  173. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +6 -4
  174. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +4 -4
  175. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +60 -0
  176. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +4 -4
  177. package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +6 -4
  178. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -4
  179. package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
  180. package/src/client/components/default/CssDefault.js +12 -12
  181. package/src/client/components/default/LogInDefault.js +6 -4
  182. package/src/client/components/default/LogOutDefault.js +6 -4
  183. package/src/client/components/default/RouterDefault.js +47 -0
  184. package/src/client/components/default/SettingsDefault.js +4 -4
  185. package/src/client/components/default/SignUpDefault.js +6 -4
  186. package/src/client/components/default/TranslateDefault.js +3 -3
  187. package/src/client/components/itemledger/{MenuItemledger.js → AppShellItemledger.js} +57 -57
  188. package/src/client/components/itemledger/CssItemledger.js +15 -15
  189. package/src/client/components/itemledger/LogInItemledger.js +6 -4
  190. package/src/client/components/itemledger/LogOutItemledger.js +6 -4
  191. package/src/client/components/itemledger/RouterItemledger.js +38 -0
  192. package/src/client/components/itemledger/SettingsItemledger.js +4 -4
  193. package/src/client/components/itemledger/SignUpItemledger.js +6 -4
  194. package/src/client/components/itemledger/TranslateItemledger.js +3 -3
  195. package/src/client/components/underpost/{MenuUnderpost.js → AppShellUnderpost.js} +88 -88
  196. package/src/client/components/underpost/CssUnderpost.js +14 -14
  197. package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +4 -4
  198. package/src/client/components/underpost/DocumentSearchProvider.js +1 -1
  199. package/src/client/components/underpost/LabGalleryUnderpost.js +12 -15
  200. package/src/client/components/underpost/LogInUnderpost.js +6 -4
  201. package/src/client/components/underpost/LogOutUnderpost.js +6 -4
  202. package/src/client/components/underpost/RouterUnderpost.js +45 -0
  203. package/src/client/components/underpost/SettingsUnderpost.js +4 -4
  204. package/src/client/components/underpost/SignUpUnderpost.js +6 -4
  205. package/src/client/components/underpost/TranslateUnderpost.js +4 -4
  206. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +235 -0
  207. package/src/client/public/cyberia-docs/ARCHITECTURE.md +443 -0
  208. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +417 -0
  209. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +313 -0
  210. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +260 -0
  211. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +241 -0
  212. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +300 -0
  213. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +279 -0
  214. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +206 -0
  215. package/src/client/public/cyberia-docs/ROADMAP.md +240 -0
  216. package/src/client/public/cyberia-docs/WHITE-PAPER.md +732 -0
  217. package/src/client/services/atlas-sprite-sheet/atlas-sprite-sheet.service.js +14 -20
  218. package/src/client/services/core/core.service.js +17 -49
  219. package/src/client/services/crypto/crypto.service.js +8 -13
  220. package/src/client/services/cyberia-action/cyberia-action.service.js +99 -0
  221. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +10 -16
  222. package/src/client/services/cyberia-entity/cyberia-entity.management.js +5 -5
  223. package/src/client/services/cyberia-entity/cyberia-entity.service.js +10 -16
  224. package/src/client/services/cyberia-instance/cyberia-instance.management.js +6 -6
  225. package/src/client/services/cyberia-instance/cyberia-instance.service.js +12 -18
  226. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +10 -16
  227. package/src/client/services/cyberia-map/cyberia-map.management.js +6 -6
  228. package/src/client/services/cyberia-map/cyberia-map.service.js +12 -18
  229. package/src/client/services/cyberia-quest/cyberia-quest.service.js +99 -0
  230. package/src/client/services/cyberia-quest-progress/cyberia-quest-progress.service.js +99 -0
  231. package/src/client/services/default/default.management.js +159 -267
  232. package/src/client/services/default/default.service.js +10 -16
  233. package/src/client/services/document/document.service.js +14 -19
  234. package/src/client/services/file/file.service.js +8 -13
  235. package/src/client/services/instance/instance.management.js +5 -5
  236. package/src/client/services/instance/instance.service.js +10 -15
  237. package/src/client/services/ipfs/ipfs.service.js +12 -18
  238. package/src/client/services/object-layer/object-layer.management.js +12 -12
  239. package/src/client/services/object-layer/object-layer.service.js +20 -26
  240. package/src/client/services/object-layer-render-frames/object-layer-render-frames.service.js +10 -16
  241. package/src/client/services/test/test.service.js +8 -13
  242. package/src/client/services/user/guest.service.js +86 -0
  243. package/src/client/services/user/user.management.js +5 -5
  244. package/src/client/services/user/user.service.js +14 -20
  245. package/src/client/ssr/body/404.js +3 -3
  246. package/src/client/ssr/body/500.js +3 -3
  247. package/src/client/ssr/body/CacheControl.js +5 -2
  248. package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
  249. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +13 -6
  250. package/src/client/ssr/head/PwaItemledger.js +197 -60
  251. package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
  252. package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
  253. package/src/client/ssr/offline/Maintenance.js +12 -11
  254. package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
  255. package/src/client/ssr/pages/Test.js +2 -2
  256. package/src/client/sw/core.sw.js +212 -0
  257. package/src/grpc/cyberia/grpc-server.js +179 -67
  258. package/src/index.js +1 -1
  259. package/src/runtime/cyberia-client/Dockerfile +80 -0
  260. package/src/runtime/cyberia-server/Dockerfile +37 -0
  261. package/src/runtime/express/Dockerfile +4 -4
  262. package/src/runtime/lampp/Dockerfile +8 -7
  263. package/src/runtime/wp/Dockerfile +11 -17
  264. package/src/server/atlas-sprite-sheet-generator.js +4 -2
  265. package/src/server/client-build-docs.js +45 -46
  266. package/src/server/client-build.js +334 -60
  267. package/src/server/client-formatted.js +47 -16
  268. package/src/server/conf.js +5 -4
  269. package/src/server/data-query.js +32 -20
  270. package/src/server/dns.js +22 -0
  271. package/src/server/ipfs-client.js +232 -91
  272. package/src/server/object-layer.js +1 -6
  273. package/src/server/process.js +13 -27
  274. package/src/server/semantic-layer-generator-floor.js +11 -51
  275. package/src/server/semantic-layer-generator-resource.js +259 -0
  276. package/src/server/semantic-layer-generator-skin.js +41 -171
  277. package/src/server/semantic-layer-generator.js +122 -14
  278. package/src/server/shape-generator.js +108 -0
  279. package/src/server/start.js +17 -3
  280. package/src/server/valkey.js +141 -235
  281. package/tsconfig.docs.json +15 -0
  282. package/typedoc.dd-cyberia.json +29 -0
  283. package/typedoc.json +29 -0
  284. package/WHITE-PAPER.md +0 -1540
  285. package/hardhat/README.md +0 -531
  286. package/hardhat/WHITE-PAPER.md +0 -1540
  287. package/jsdoc.dd-cyberia.json +0 -68
  288. package/jsdoc.json +0 -68
  289. package/src/api/object-layer/README.md +0 -672
  290. package/src/client/components/core/ColorPalette.js +0 -5267
  291. package/src/client/components/core/JoyStick.js +0 -80
  292. package/src/client/components/cryptokoyn/RoutesCryptokoyn.js +0 -39
  293. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +0 -62
  294. package/src/client/components/cyberia-portal/ServerCyberiaPortal.js +0 -136
  295. package/src/client/components/default/RoutesDefault.js +0 -49
  296. package/src/client/components/itemledger/RoutesItemledger.js +0 -40
  297. package/src/client/components/underpost/RoutesUnderpost.js +0 -47
  298. package/src/client/sw/default.sw.js +0 -127
  299. package/src/client/sw/template.sw.js +0 -84
  300. package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +0 -305
  301. package/src/grpc/cyberia/README.md +0 -326
@@ -324,59 +324,56 @@ const buildApiDocs = async ({
324
324
  };
325
325
 
326
326
  /**
327
- * Builds JSDoc documentation
327
+ * Builds API documentation using TypeDoc (generates a modern static site from JSDoc-annotated JS).
328
+ * Config is read from the base typedoc JSON, merged with runtime values, written to a temporary
329
+ * file, and deleted after the build — the base config file is never mutated on disk.
328
330
  * @function buildJsDocs
329
331
  * @memberof clientBuildDocs
330
- * @param {Object} options - JSDoc build options
332
+ * @param {Object} options - TypeDoc build options
331
333
  * @param {string} options.host - The hostname for the documentation
332
334
  * @param {string} options.path - The base path for the documentation
333
335
  * @param {Object} options.metadata - Metadata for the documentation
334
- * @param {string} options.publicClientId - Client ID used to resolve the tutorials/references directory
336
+ * @param {string} options.publicClientId - Client ID used to resolve the references directory
335
337
  * @param {Object} options.docs - Documentation config from server conf
336
- * @param {string} options.docs.jsJsonPath - Path to the JSDoc JSON config file
338
+ * @param {string} options.docs.jsJsonPath - Path to the base typedoc JSON config file
339
+ * @param {string} options.docsDestination - Resolved output path for the generated docs
337
340
  */
338
- const buildJsDocs = async ({ host, path, metadata = {}, publicClientId, docs }) => {
341
+ const buildJsDocs = async ({ host, path, metadata = {}, publicClientId, docs, docsDestination }) => {
339
342
  const logger = loggerFactory(import.meta);
340
343
 
341
- const jsDocSourcePath = docs.jsJsonPath;
342
- if (!fs.existsSync(jsDocSourcePath)) {
343
- logger.warn('jsdoc config not found, skipping', jsDocSourcePath);
344
+ const typedocConfigPath = docs.jsJsonPath;
345
+ if (!fs.existsSync(typedocConfigPath)) {
346
+ logger.warn('typedoc config not found, skipping', typedocConfigPath);
344
347
  return;
345
348
  }
346
- const jsDocsConfig = JSON.parse(fs.readFileSync(jsDocSourcePath, 'utf8'));
347
- logger.info('using jsdoc config', jsDocSourcePath);
349
+ const baseConfig = JSON.parse(fs.readFileSync(typedocConfigPath, 'utf8'));
350
+ logger.info('using typedoc config', typedocConfigPath);
348
351
 
349
- jsDocsConfig.opts.destination = `./public/${host}${path === '/' ? path : `${path}/`}docs/`;
350
- jsDocsConfig.opts.theme_opts.title = metadata?.title ? metadata.title : undefined;
351
- jsDocsConfig.opts.theme_opts.favicon = `./public/${host}${path === '/' ? '/' : `${path}/`}favicon.ico`;
352
-
353
- const tutorialsPath = `./src/client/public/${publicClientId}/docs/references`;
352
+ // Build runtime config in memory never mutate the base config file
353
+ // tsconfig must be absolute so TypeDoc resolves it regardless of where the
354
+ // tmp config file is located on disk.
355
+ const runtimeConfig = {
356
+ ...baseConfig,
357
+ tsconfig: fs.realpathSync(baseConfig.tsconfig || './tsconfig.docs.json'),
358
+ out: docsDestination,
359
+ name: metadata?.title || baseConfig.name,
360
+ favicon: `./public/${host}${path === '/' ? '/' : `${path}/`}favicon.ico`,
361
+ };
354
362
 
363
+ // Include extra reference documents as TypeDoc document pages
364
+ // TypeDoc 0.28+: option is `projectDocuments`, not `documents`
355
365
  if (Array.isArray(docs.references) && docs.references.length > 0) {
356
- fs.mkdirSync(tutorialsPath, { recursive: true });
357
- for (const refPath of docs.references) {
358
- if (fs.existsSync(refPath)) {
359
- const fileName = refPath.split('/').pop();
360
- fs.copySync(refPath, `${tutorialsPath}/${fileName}`);
361
- logger.info('copied reference to tutorials', refPath);
362
- }
363
- }
366
+ runtimeConfig.projectDocuments = docs.references.filter((p) => fs.existsSync(p));
367
+ if (runtimeConfig.projectDocuments.length > 0) logger.info('typedoc documents', runtimeConfig.projectDocuments);
364
368
  }
365
369
 
366
- if (fs.existsSync(tutorialsPath) && fs.readdirSync(tutorialsPath).length > 0) {
367
- jsDocsConfig.opts.tutorials = tutorialsPath;
368
- if (jsDocsConfig.opts.theme_opts.sections && !jsDocsConfig.opts.theme_opts.sections.includes('Tutorials')) {
369
- jsDocsConfig.opts.theme_opts.sections.push('Tutorials');
370
- }
371
- logger.info('build jsdoc tutorials', tutorialsPath);
372
- } else {
373
- delete jsDocsConfig.opts.tutorials;
374
- }
370
+ const tmpConfigPath = `.typedoc.tmp.json`;
371
+ fs.writeFileSync(tmpConfigPath, JSON.stringify(runtimeConfig, null, 2), 'utf8');
372
+ logger.warn('build typedoc view', docsDestination);
375
373
 
376
- fs.writeFileSync(jsDocSourcePath, JSON.stringify(jsDocsConfig, null, 4), 'utf8');
377
- logger.warn('build jsdoc view', jsDocsConfig.opts.destination);
374
+ shellExec(`node_modules/.bin/typedoc --options ${tmpConfigPath}`, { silent: true });
378
375
 
379
- shellExec(`npx jsdoc -c ${jsDocSourcePath}`, { silent: true });
376
+ fs.removeSync(tmpConfigPath);
380
377
  };
381
378
 
382
379
  /**
@@ -384,17 +381,13 @@ const buildJsDocs = async ({ host, path, metadata = {}, publicClientId, docs })
384
381
  * @function buildCoverage
385
382
  * @memberof clientBuildDocs
386
383
  * @param {Object} options - Coverage build options
387
- * @param {string} options.host - The hostname for the coverage
388
- * @param {string} options.path - The base path for the coverage
389
384
  * @param {Object} options.docs - Documentation config from server conf
390
- * @param {string} options.docs.coveragePath - Directory where to run npm run coverage
385
+ * @param {string} options.docs.coveragePath - Directory where coverage reports are generated
386
+ * @param {string} options.docsDestination - Resolved output path where docs were built
391
387
  */
392
- const buildCoverage = async ({ host, path, docs }) => {
388
+ const buildCoverage = async ({ docs, docsDestination }) => {
393
389
  const logger = loggerFactory(import.meta);
394
- const jsDocSourcePath = docs.jsJsonPath;
395
- const jsDocsConfig = JSON.parse(fs.readFileSync(jsDocSourcePath, 'utf8'));
396
- const coveragePath = docs.coveragePath;
397
- const coverageOutputDir = docs.coverageOutputDir || 'coverage';
390
+ const { coveragePath, coverageOutputDir = 'coverage' } = docs;
398
391
 
399
392
  const coverageOutputPath = `${coveragePath}/coverage`;
400
393
  if (!fs.existsSync(coverageOutputPath)) {
@@ -412,7 +405,7 @@ const buildCoverage = async ({ host, path, docs }) => {
412
405
  }
413
406
 
414
407
  if (fs.existsSync(coverageOutputPath) && fs.readdirSync(coverageOutputPath).length > 0) {
415
- const coverageBuildPath = `${jsDocsConfig.opts.destination}${coverageOutputDir}`;
408
+ const coverageBuildPath = `${docsDestination}${coverageOutputDir}`;
416
409
  fs.mkdirSync(coverageBuildPath, { recursive: true });
417
410
  // Hardhat 3 outputs HTML to coverage/html/; Hardhat 2 / c8 output directly to coverage/
418
411
  const coverageHtmlSubdir = `${coverageOutputPath}/html`;
@@ -453,8 +446,14 @@ const buildDocs = async ({
453
446
  packageData,
454
447
  docs,
455
448
  }) => {
456
- await buildJsDocs({ host, path, metadata, publicClientId, docs });
457
- await buildCoverage({ host, path, docs });
449
+ const pathPrefix = path === '/' ? '/' : `${path}/`;
450
+ // TypeDoc output is versioned: served at /docs/engine/{version}/
451
+ const version = (packageData?.version || '').replace(/^v/, '');
452
+ const jsDocsDestination = `./public/${host}${pathPrefix}docs/engine/${version}/`;
453
+ // Coverage output at /docs/coverage/ (or /docs/{coverageOutputDir}/)
454
+ const coverageBaseDestination = `./public/${host}${pathPrefix}docs/`;
455
+ await buildJsDocs({ host, path, metadata, publicClientId, docs, docsDestination: jsDocsDestination });
456
+ await buildCoverage({ docs, docsDestination: coverageBaseDestination });
458
457
  await buildApiDocs({
459
458
  host,
460
459
  path,
@@ -38,6 +38,8 @@ import { ssrFactory } from './ssr.js';
38
38
  * @memberof clientBuild
39
39
  */
40
40
  const copyNonExistingFiles = (src, dest) => {
41
+ if (dir.basename(src) === '.git') return;
42
+
41
43
  // Ensure source exists
42
44
  if (!fs.existsSync(src)) {
43
45
  throw new Error(`Source directory does not exist: ${src}`);
@@ -74,6 +76,224 @@ const copyNonExistingFiles = (src, dest) => {
74
76
  }
75
77
  };
76
78
 
79
+ const splitFileByMb = ({ filePath, partSizeMb, logger }) => {
80
+ const partSizeBytes = Math.floor(Number(partSizeMb) * 1024 * 1024);
81
+ if (!Number.isFinite(partSizeBytes) || partSizeBytes <= 0) {
82
+ throw new Error(`Invalid --split value: ${partSizeMb}`);
83
+ }
84
+
85
+ // Clean ALL stale part files (any naming variant) before writing new ones
86
+ const zipDir = dir.dirname(filePath);
87
+ const zipBase = dir.basename(filePath);
88
+ if (fs.existsSync(zipDir)) {
89
+ fs.readdirSync(zipDir)
90
+ .filter((name) => name.startsWith(`${zipBase}.part`) || name.startsWith(`${zipBase}-part`))
91
+ .forEach((name) => fs.removeSync(dir.join(zipDir, name)));
92
+ }
93
+
94
+ const fileBuffer = fs.readFileSync(filePath);
95
+ const partPaths = [];
96
+
97
+ for (let offset = 0, partIndex = 0; offset < fileBuffer.length; offset += partSizeBytes, partIndex++) {
98
+ const partBuffer = fileBuffer.subarray(offset, offset + partSizeBytes);
99
+ const partPath = `${filePath}.part${String(partIndex + 1).padStart(3, '0')}`;
100
+ fs.writeFileSync(partPath, partBuffer);
101
+ partPaths.push(partPath);
102
+ }
103
+
104
+ logger.warn('split zip', {
105
+ filePath,
106
+ partSizeMb: Number(partSizeMb),
107
+ parts: partPaths.length,
108
+ });
109
+
110
+ return partPaths;
111
+ };
112
+
113
+ const getZipPartPaths = (zipPath) => {
114
+ const zipDir = dir.dirname(zipPath);
115
+ const zipBase = dir.basename(zipPath);
116
+ const partPrefixDot = `${zipBase}.part`;
117
+ const partPrefixDash = `${zipBase}-part`;
118
+
119
+ const parsePartIndex = (rawSuffix) => {
120
+ // Strip optional .zip suffix added by pull/download (e.g. '001.zip' → '001')
121
+ const digits = rawSuffix.replace(/\.zip$/i, '');
122
+ return /^\d+$/.test(digits) ? Number(digits) : NaN;
123
+ };
124
+
125
+ const getPartIndex = (name) => {
126
+ if (name.startsWith(partPrefixDot)) return parsePartIndex(name.slice(partPrefixDot.length));
127
+ if (name.startsWith(partPrefixDash)) return parsePartIndex(name.slice(partPrefixDash.length));
128
+ return NaN;
129
+ };
130
+
131
+ return fs
132
+ .readdirSync(zipDir)
133
+ .filter((name) => Number.isFinite(getPartIndex(name)))
134
+ .sort((a, b) => getPartIndex(a) - getPartIndex(b))
135
+ .map((name) => dir.join(zipDir, name));
136
+ };
137
+
138
+ const resolveClientBuildZip = (buildPrefix) => {
139
+ const normalizedPrefix = buildPrefix.replace(/\.zip(?:[.-]part\d+|[.-]part\*)?$/, '').replace(/[.-]part\*$/, '');
140
+ const candidatePrefixes = uniqueArray([
141
+ normalizedPrefix,
142
+ normalizedPrefix.endsWith('-') ? normalizedPrefix : `${normalizedPrefix}-`,
143
+ ]);
144
+
145
+ for (const prefix of candidatePrefixes) {
146
+ const zipPath = `${prefix}.zip`;
147
+ if (fs.existsSync(zipPath)) {
148
+ return {
149
+ buildPrefix: prefix,
150
+ zipPath,
151
+ partPaths: [],
152
+ };
153
+ }
154
+
155
+ const partPaths = fs.existsSync(dir.dirname(zipPath)) ? getZipPartPaths(zipPath) : [];
156
+ if (partPaths.length > 0) {
157
+ return {
158
+ buildPrefix: prefix,
159
+ zipPath,
160
+ partPaths,
161
+ };
162
+ }
163
+ }
164
+
165
+ const searchDir = dir.dirname(normalizedPrefix);
166
+ const prefixBase = dir.basename(normalizedPrefix);
167
+ if (!fs.existsSync(searchDir)) {
168
+ throw new Error(`Build directory not found: ${searchDir}`);
169
+ }
170
+
171
+ const matches = uniqueArray(
172
+ fs
173
+ .readdirSync(searchDir)
174
+ .filter((name) => name.startsWith(prefixBase) && /\.zip(?:[.-]part\d+)?$/.test(name))
175
+ .map((name) => name.replace(/[.-]part\d+$/, '')),
176
+ );
177
+
178
+ if (matches.length === 1) {
179
+ const zipPath = dir.join(searchDir, matches[0]);
180
+ const partPaths = getZipPartPaths(zipPath);
181
+ return {
182
+ buildPrefix: zipPath.replace(/\.zip$/, ''),
183
+ zipPath,
184
+ partPaths,
185
+ };
186
+ }
187
+
188
+ if (matches.length > 1) {
189
+ throw new Error(
190
+ `Multiple build zip matches found for '${buildPrefix}': ${matches.join(', ')}. Use a more specific --unzip path.`,
191
+ );
192
+ }
193
+
194
+ throw new Error(`No build zip or split parts found for: ${buildPrefix}`);
195
+ };
196
+
197
+ /**
198
+ * Merges split ZIP parts back into a single ZIP file.
199
+ * @param {object} options
200
+ * @param {string} options.buildPrefix - The build prefix path (e.g. build/underpost.net/underpost.net-).
201
+ * @param {object} options.logger - Logger instance.
202
+ * @returns {{ zipPath: string, partPaths: string[], mergedBytes: number }}
203
+ */
204
+ const mergeClientBuildZip = ({ buildPrefix, logger }) => {
205
+ // Normalize to get the zip path, then look for parts directly (bypassing resolveClientBuildZip
206
+ // which prefers an existing monolithic zip over parts).
207
+ const normalizedPrefix = buildPrefix.replace(/\.zip(?:[.-]part\d+)?$/, '').replace(/[-.]$/, '') + '-';
208
+ const candidatePrefixes = uniqueArray([buildPrefix, buildPrefix.endsWith('-') ? buildPrefix : `${buildPrefix}-`]);
209
+
210
+ let zipPath;
211
+ let partPaths = [];
212
+
213
+ for (const prefix of candidatePrefixes) {
214
+ const candidate = prefix.endsWith('.zip') ? prefix : `${prefix}.zip`;
215
+ const parts = getZipPartPaths(candidate);
216
+ if (parts.length > 0) {
217
+ zipPath = candidate;
218
+ partPaths = parts;
219
+ break;
220
+ }
221
+ }
222
+
223
+ if (partPaths.length === 0) {
224
+ // Fall back to resolveClientBuildZip for the zipPath
225
+ const resolved = resolveClientBuildZip(buildPrefix);
226
+ zipPath = resolved.zipPath;
227
+ logger.warn('merge-zip: no split parts found, nothing to merge', { buildPrefix, zipPath });
228
+ return { zipPath, partPaths, mergedBytes: 0 };
229
+ }
230
+
231
+ // For each part, extract raw bytes: if the part file is a Cloudinary wrapper zip
232
+ // (downloaded via pull without --omit-unzip or with --omit-unzip keeping the .zip),
233
+ // extract the inner entry rather than using the wrapper bytes.
234
+ const readPartBytes = (partPath) => {
235
+ const rawBytes = fs.readFileSync(partPath);
236
+ // Check for ZIP magic bytes (PK\x03\x04)
237
+ if (rawBytes[0] === 0x50 && rawBytes[1] === 0x4b && rawBytes[2] === 0x03 && rawBytes[3] === 0x04) {
238
+ try {
239
+ const wrapperZip = new AdmZip(rawBytes);
240
+ const entries = wrapperZip.getEntries();
241
+ // The inner entry is the original part file (without the outer .zip wrapper)
242
+ const partBase = dir.basename(partPath).replace(/\.zip$/i, '');
243
+ const entry = entries.find((e) => e.entryName === partBase || e.entryName.endsWith('/' + partBase));
244
+ if (entry) {
245
+ return entry.getData();
246
+ }
247
+ // Fallback: single-entry archive
248
+ if (entries.length === 1) {
249
+ return entries[0].getData();
250
+ }
251
+ } catch (_) {
252
+ // Not a valid zip or extraction failed — use raw bytes
253
+ }
254
+ }
255
+ return rawBytes;
256
+ };
257
+
258
+ const mergedBuffer = Buffer.concat(partPaths.map(readPartBytes));
259
+ fs.writeFileSync(zipPath, mergedBuffer);
260
+
261
+ logger.warn('merge-zip: merged split parts into zip', {
262
+ zipPath,
263
+ parts: partPaths.length,
264
+ mergedBytes: mergedBuffer.length,
265
+ });
266
+
267
+ return { zipPath, partPaths, mergedBytes: mergedBuffer.length };
268
+ };
269
+
270
+ const unzipClientBuild = ({ buildPrefix, logger }) => {
271
+ const { zipPath, partPaths, buildPrefix: resolvedBuildPrefix } = resolveClientBuildZip(buildPrefix);
272
+ const outputPath = resolvedBuildPrefix.replace(/-$/, '');
273
+
274
+ fs.removeSync(outputPath);
275
+ fs.mkdirSync(outputPath, { recursive: true });
276
+
277
+ const zip =
278
+ partPaths.length > 0
279
+ ? new AdmZip(Buffer.concat(partPaths.map((partPath) => fs.readFileSync(partPath))))
280
+ : new AdmZip(zipPath);
281
+
282
+ zip.extractAllTo(outputPath, true);
283
+
284
+ logger.warn('unzip build', {
285
+ source: partPaths.length > 0 ? partPaths : [zipPath],
286
+ outputPath,
287
+ splitParts: partPaths.length,
288
+ });
289
+
290
+ return {
291
+ outputPath,
292
+ zipPath,
293
+ partPaths,
294
+ };
295
+ };
296
+
77
297
  /** @type {string} Default XSL sitemap template used when no `sitemap` source file exists in the public directory. */
78
298
  const defaultSitemapXsl = `<?xml version="1.0" encoding="UTF-8"?>
79
299
  <xsl:stylesheet version="1.0"
@@ -233,6 +453,7 @@ const defaultSitemapXsl = `<?xml version="1.0" encoding="UTF-8"?>
233
453
  * @param {Array} options.liveClientBuildPaths - List of paths to build incrementally.
234
454
  * @param {Array} options.instances - List of instances to build.
235
455
  * @param {boolean} options.buildZip - Whether to create zip files of the builds.
456
+ * @param {string|number} options.split - Optional zip split size in MB.
236
457
  * @param {boolean} options.fullBuild - Whether to perform a full build.
237
458
  * @param {boolean} options.iconsBuild - Whether to build icons.
238
459
  * @returns {Promise<void>} - Promise that resolves when the build is complete.
@@ -245,6 +466,7 @@ const buildClient = async (
245
466
  liveClientBuildPaths: [],
246
467
  instances: [],
247
468
  buildZip: false,
469
+ split: '',
248
470
  fullBuild: false,
249
471
  iconsBuild: false,
250
472
  },
@@ -311,35 +533,18 @@ const buildClient = async (
311
533
 
312
534
  buildAcmeChallengePath(acmeChallengeFullPath);
313
535
 
314
- if (publicClientId && publicClientId.startsWith('html-website-templates')) {
315
- if (!fs.existsSync(`/home/dd/html-website-templates/`))
316
- shellExec(`cd /home/dd && git clone https://github.com/designmodo/html-website-templates.git`);
317
- if (!fs.existsSync(`${rootClientPath}/index.php`)) {
318
- fs.copySync(`/home/dd/html-website-templates/${publicClientId.split('-publicClientId-')[1]}`, rootClientPath);
319
- Underpost.repo.initLocalRepo({ path: rootClientPath });
320
- shellExec(`cd ${rootClientPath} && git add . && git commit -m "Base template implementation"`);
321
- // git remote add origin git@github.com:<username>/<repo>.git
322
- fs.writeFileSync(`${rootClientPath}/.git/.htaccess`, `Deny from all`, 'utf8');
323
- }
324
- return;
325
- }
326
-
327
536
  fs.removeSync(rootClientPath);
328
537
 
329
538
  if (fs.existsSync(`./src/client/public/${publicClientId}`)) {
330
539
  if (iconsBuild === true) await buildIcons({ publicClientId, metadata });
331
540
 
332
- fs.copySync(
333
- `./src/client/public/${publicClientId}`,
334
- rootClientPath /* {
335
- filter: function (name) {
336
- console.log(name);
337
- return true;
338
- },
339
- } */,
340
- );
541
+ fs.copySync(`./src/client/public/${publicClientId}`, rootClientPath, {
542
+ filter: (sourcePath) => !sourcePath.split(dir.sep).includes('.git'),
543
+ });
341
544
  } else if (fs.existsSync(`./engine-private/src/client/public/${publicClientId}`)) {
342
- fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath);
545
+ fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath, {
546
+ filter: (sourcePath) => !sourcePath.split(dir.sep).includes('.git'),
547
+ });
343
548
  }
344
549
  if (dists)
345
550
  for (const dist of dists) {
@@ -463,28 +668,18 @@ const buildClient = async (
463
668
  for (const module of services) {
464
669
  if (!fs.existsSync(`${rootClientPath}/services/${module}`))
465
670
  fs.mkdirSync(`${rootClientPath}/services/${module}`, { recursive: true });
671
+ const moduleDir = `./src/client/services/${module}`;
672
+ if (!fs.existsSync(moduleDir)) continue;
466
673
 
467
- if (fs.existsSync(`./src/client/services/${module}/${module}.service.js`)) {
468
- const jsSrcPath = `./src/client/services/${module}/${module}.service.js`;
469
- const jsPublicPath = `${rootClientPath}/services/${module}/${module}.service.js`;
470
- if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
674
+ const serviceFiles = fs
675
+ .readdirSync(moduleDir)
676
+ .filter((name) => name.endsWith('.service.js') || name.endsWith('.management.js'))
677
+ .sort();
471
678
 
472
- const jsSrc = await transformClientJs(jsSrcPath, {
473
- dists,
474
- proxyPath: path,
475
- basePath: 'services',
476
- module,
477
- baseHost,
478
- minify: minifyBuild,
479
- });
480
- fs.writeFileSync(jsPublicPath, jsSrc, 'utf8');
481
- }
482
- }
679
+ for (const serviceFile of serviceFiles) {
680
+ const jsSrcPath = `${moduleDir}/${serviceFile}`;
681
+ const jsPublicPath = `${rootClientPath}/services/${module}/${serviceFile}`;
483
682
 
484
- for (const module of services) {
485
- if (fs.existsSync(`./src/client/services/${module}/${module}.management.js`)) {
486
- const jsSrcPath = `./src/client/services/${module}/${module}.management.js`;
487
- const jsPublicPath = `${rootClientPath}/services/${module}/${module}.management.js`;
488
683
  if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
489
684
 
490
685
  const jsSrc = await transformClientJs(jsSrcPath, {
@@ -497,6 +692,30 @@ const buildClient = async (
497
692
  });
498
693
  fs.writeFileSync(jsPublicPath, jsSrc, 'utf8');
499
694
  }
695
+
696
+ // Auto-build guest module files when user module is processed
697
+ if (module === 'user') {
698
+ const guestModuleDir = './src/client/services/user';
699
+ const guestServicePath = `${guestModuleDir}/guest.service.js`;
700
+ if (fs.existsSync(guestServicePath)) {
701
+ if (!fs.existsSync(`${rootClientPath}/services/user`))
702
+ fs.mkdirSync(`${rootClientPath}/services/user`, { recursive: true });
703
+
704
+ const guestJsPublicPath = `${rootClientPath}/services/user/guest.service.js`;
705
+
706
+ if (!enableLiveRebuild || options.liveClientBuildPaths.find((p) => p.srcBuildPath === guestServicePath)) {
707
+ const guestJsSrc = await transformClientJs(guestServicePath, {
708
+ dists,
709
+ proxyPath: path,
710
+ basePath: 'services',
711
+ module: 'user',
712
+ baseHost,
713
+ minify: minifyBuild,
714
+ });
715
+ fs.writeFileSync(guestJsPublicPath, guestJsSrc, 'utf8');
716
+ }
717
+ }
718
+ }
500
719
  }
501
720
  }
502
721
 
@@ -506,14 +725,18 @@ const buildClient = async (
506
725
  const Render = await ssrFactory();
507
726
 
508
727
  if (views) {
509
- const jsSrcPath = fs.existsSync(`./src/client/sw/${publicClientId}.sw.js`)
510
- ? `./src/client/sw/${publicClientId}.sw.js`
511
- : `./src/client/sw/default.sw.js`;
728
+ const jsSrcPath = `./src/client/sw/core.sw.js`;
512
729
 
513
730
  const jsPublicPath = `${rootClientPath}/sw.js`;
514
731
 
515
732
  if (!(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath))) {
516
- const jsSrc = await transformClientJs(jsSrcPath, { dists, proxyPath: path, baseHost, minify: minifyBuild });
733
+ const jsSrc = await transformClientJs(jsSrcPath, {
734
+ dists,
735
+ proxyPath: path,
736
+ baseHost,
737
+ minify: minifyBuild,
738
+ externalizeBareImports: false,
739
+ });
517
740
 
518
741
  fs.writeFileSync(jsPublicPath, jsSrc, 'utf8');
519
742
  }
@@ -746,16 +969,18 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
746
969
  if (client) {
747
970
  let PRE_CACHED_RESOURCES = [];
748
971
 
749
- if (views && fs.existsSync(`${rootClientPath}/sw.js`)) {
750
- PRE_CACHED_RESOURCES = await fs.readdir(rootClientPath, { recursive: true });
751
- PRE_CACHED_RESOURCES = views
752
- .map((view) => `${path === '/' ? '' : path}${view.path}`)
753
- .concat(
754
- PRE_CACHED_RESOURCES.map((p) => `/${p}`).filter(
755
- (p) => p[1] !== '.' && !fs.statSync(`${rootClientPath}${p}`).isDirectory(),
756
- ),
757
- );
758
- }
972
+ const normalizePrecacheRoutePath = (candidatePath) => {
973
+ const routePath =
974
+ typeof candidatePath === 'string' && candidatePath.trim().length > 0 ? candidatePath.trim() : '/offline';
975
+ const withLeadingSlash = routePath.startsWith('/') ? routePath : `/${routePath}`;
976
+ const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, '');
977
+ return withoutTrailingSlash.length > 0 ? withoutTrailingSlash : '/';
978
+ };
979
+
980
+ const toPrecacheIndexUrl = (routePath) => {
981
+ const normalizedRoutePath = normalizePrecacheRoutePath(routePath);
982
+ return `${path === '/' ? '' : path}${normalizedRoutePath === '/' ? '' : normalizedRoutePath}/index.html`;
983
+ };
759
984
 
760
985
  for (const pageType of ['offline', 'pages']) {
761
986
  if (confSSR[getCapVariableName(client)] && confSSR[getCapVariableName(client)][pageType]) {
@@ -783,7 +1008,11 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
783
1008
  rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
784
1009
  }${page.path === '/' ? page.path : `${page.path}/`}`;
785
1010
 
786
- PRE_CACHED_RESOURCES.push(`${path === '/' ? '' : path}${page.path === '/' ? '' : page.path}/index.html`);
1011
+ // Install-time precache is intentionally restricted to SSR offline pages.
1012
+ // All other routes/assets are loaded lazily at runtime.
1013
+ if (pageType === 'offline') {
1014
+ PRE_CACHED_RESOURCES.push(toPrecacheIndexUrl(page.path));
1015
+ }
787
1016
 
788
1017
  if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
789
1018
 
@@ -809,13 +1038,47 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
809
1038
  }
810
1039
 
811
1040
  {
1041
+ const cacheScope = path === '/' ? 'root' : path.replaceAll('/', '_');
1042
+ const ssrClientConf = confSSR[getCapVariableName(client)] || {};
1043
+ const ssrOfflinePages = Array.isArray(ssrClientConf.offline) ? ssrClientConf.offline : [];
1044
+ const normalizeSsrRoutePath = (candidatePath, fallbackPath) => {
1045
+ const value =
1046
+ typeof candidatePath === 'string' && candidatePath.trim().length > 0
1047
+ ? candidatePath.trim()
1048
+ : fallbackPath;
1049
+ const withLeadingSlash = value.startsWith('/') ? value : `/${value}`;
1050
+ const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, '');
1051
+ return withoutTrailingSlash.length > 0 ? withoutTrailingSlash : '/';
1052
+ };
1053
+
1054
+ const offlineSsrPage =
1055
+ ssrOfflinePages.find(
1056
+ (page) =>
1057
+ page?.client === 'NoNetworkConnection' ||
1058
+ /no\s*network|offline/i.test(`${page?.title || ''} ${page?.client || ''} ${page?.path || ''}`),
1059
+ ) || ssrOfflinePages[0];
1060
+
1061
+ const maintenanceSsrPage =
1062
+ ssrOfflinePages.find(
1063
+ (page) =>
1064
+ page?.client === 'Maintenance' ||
1065
+ /maintenance/i.test(`${page?.title || ''} ${page?.client || ''} ${page?.path || ''}`),
1066
+ ) || ssrOfflinePages[1];
1067
+
1068
+ const offlinePath = normalizeSsrRoutePath(offlineSsrPage?.path, '/offline');
1069
+ const maintenancePath = normalizeSsrRoutePath(maintenanceSsrPage?.path, '/maintenance');
1070
+
812
1071
  const renderPayload = {
813
1072
  PRE_CACHED_RESOURCES: uniqueArray(PRE_CACHED_RESOURCES),
814
1073
  PROXY_PATH: path,
1074
+ CACHE_PREFIX: `engine-core-v3-${cacheScope}`,
1075
+ OFFLINE_PATH: offlinePath,
1076
+ MAINTENANCE_PATH: maintenancePath,
815
1077
  };
816
1078
  fs.writeFileSync(
817
1079
  `${rootClientPath}/sw.js`,
818
1080
  `self.renderPayload = ${JSONweb(renderPayload)};
1081
+ self.__WB_DISABLE_DEV_LOGS = true;
819
1082
  ${fs.readFileSync(`${rootClientPath}/sw.js`, 'utf8')}`,
820
1083
  'utf8',
821
1084
  );
@@ -838,13 +1101,24 @@ ${fs.readFileSync(`${rootClientPath}/sw.js`, 'utf8')}`,
838
1101
  }
839
1102
 
840
1103
  const buildId = `${host}-${path.replaceAll('/', '')}`;
1104
+ const zipPath = `./build/${buildId}.zip`;
841
1105
 
842
- logger.warn('write zip', `./build/${buildId}.zip`);
1106
+ logger.warn('write zip', zipPath);
843
1107
 
844
- zip.writeZip(`./build/${buildId}.zip`);
1108
+ zip.writeZip(zipPath);
1109
+
1110
+ if (options.split) {
1111
+ splitFileByMb({
1112
+ filePath: zipPath,
1113
+ partSizeMb: options.split,
1114
+ logger,
1115
+ });
1116
+ fs.removeSync(zipPath);
1117
+ logger.warn('removed original zip after split', { zipPath });
1118
+ }
845
1119
  }
846
1120
  }
847
1121
  }
848
1122
  };
849
1123
 
850
- export { buildClient, copyNonExistingFiles };
1124
+ export { buildClient, copyNonExistingFiles, unzipClientBuild, mergeClientBuildZip };