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
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Underpost platform content catalog — the base `pwa-microservices-template`.
3
+ *
4
+ * @module src/server/catalog-underpost.js
5
+ * @namespace UnderpostCatalog
6
+ */
7
+
8
+ /**
9
+ * Workflow + service files re-added to the template after the engine-only strip.
10
+ * @constant {string[]}
11
+ * @memberof UnderpostCatalog
12
+ */
13
+ const TEMPLATE_RESTORE_PATHS = [
14
+ `./src/server/catalog-underpost.js`,
15
+ `./.github/workflows/pwa-microservices-template-page.cd.yml`,
16
+ `./.github/workflows/pwa-microservices-template-test.ci.yml`,
17
+ `./.github/workflows/npmpkg.ci.yml`,
18
+ `./.github/workflows/ghpkg.ci.yml`,
19
+ `./.github/workflows/gitlab.ci.yml`,
20
+ `./.github/workflows/publish.ci.yml`,
21
+ `./.github/workflows/release.cd.yml`,
22
+ `./src/client/services/user/guest.service.js`,
23
+ './src/api/user/guest.service.js',
24
+ './src/ws/IoInterface.js',
25
+ './src/ws/IoServer.js',
26
+ './manifests/deployment/dd-default-development',
27
+ ];
28
+
29
+ /**
30
+ * npm keywords for the standalone Underpost platform / template package.
31
+ * @constant {string[]}
32
+ * @memberof UnderpostCatalog
33
+ */
34
+ const TEMPLATE_KEYWORDS = [
35
+ 'underpost',
36
+ 'underpost-platform',
37
+ 'cli',
38
+ 'toolchain',
39
+ 'ci-cd',
40
+ 'devops',
41
+ 'kubernetes',
42
+ 'k3s',
43
+ 'kubeadm',
44
+ 'lxd',
45
+ 'baremetal',
46
+ 'container-orchestration',
47
+ 'image-management',
48
+ 'pwa',
49
+ 'workbox',
50
+ 'microservices',
51
+ ];
52
+
53
+ /**
54
+ * npm description for the standalone Underpost platform / template package.
55
+ * @constant {string}
56
+ * @memberof UnderpostCatalog
57
+ */
58
+ const TEMPLATE_DESCRIPTION =
59
+ 'Underpost Platform — end-to-end CI/CD and application-delivery toolchain CLI. Covers bare metal, Kubernetes, K3s, kubeadm, LXD, container/image orchestration, secrets, databases, cron jobs, monitoring, SSH, runners, PWA + Workbox delivery, and release orchestration. Extensible via downstream CLIs.';
60
+
61
+ export { TEMPLATE_RESTORE_PATHS, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Dynamic product-catalog resolver.
3
+ *
4
+ * Product catalogs (`catalog-<suffix>.js`, e.g. `catalog-cyberia`, `catalog-prototype`)
5
+ * are loaded lazily by deploy id via ES dynamic `import()` so the base build
6
+ * (`bin/build`) and template assembly (`bin/build.template`) never statically
7
+ * depend on any product module. Removing a product catalog simply makes its
8
+ * deploy id resolve to the empty catalog — nothing else breaks.
9
+ *
10
+ * Each product catalog default-exports the uniform shape documented in
11
+ * {@link module:src/server/catalog-cyberia}.
12
+ *
13
+ * @module src/server/catalog.js
14
+ * @namespace Catalog
15
+ */
16
+
17
+ import fs from 'fs-extra';
18
+ import { fileURLToPath } from 'node:url';
19
+ import * as path from 'node:path';
20
+
21
+ const catalogDir = path.dirname(fileURLToPath(import.meta.url));
22
+
23
+ /** Empty product catalog returned for deploy ids without a dedicated module. */
24
+ const EMPTY_CATALOG = {
25
+ sourceMoves: [],
26
+ privateConfPaths: [],
27
+ templatePaths: [],
28
+ stripPaths: [],
29
+ keywords: [],
30
+ description: '',
31
+ };
32
+
33
+ /**
34
+ * Loads a single deploy id's product catalog. The suffix after `dd-` selects the
35
+ * module (`dd-cyberia` → `catalog-cyberia.js`). Returns {@link EMPTY_CATALOG} when
36
+ * the deploy id has no dedicated catalog or the module cannot be loaded.
37
+ *
38
+ * @method loadDeployCatalog
39
+ * @param {string} deployId - A concrete deploy id (e.g. `dd-cyberia`).
40
+ * @returns {Promise<object>} The product catalog (uniform shape).
41
+ * @memberof Catalog
42
+ */
43
+ const loadDeployCatalog = async (deployId) => {
44
+ const suffix = (deployId ?? '').split('dd-')[1];
45
+ if (!suffix) return EMPTY_CATALOG;
46
+ try {
47
+ const mod = await import(`./catalog-${suffix}.js`);
48
+ return { ...EMPTY_CATALOG, ...(mod.default ?? {}) };
49
+ } catch {
50
+ return EMPTY_CATALOG;
51
+ }
52
+ };
53
+
54
+ /**
55
+ * Loads every product catalog present alongside this module (`catalog-*.js`,
56
+ * excluding the base `catalog-underpost` and this resolver). Used to aggregate
57
+ * product `stripPaths` for the base template without naming any product.
58
+ *
59
+ * @method loadProductCatalogs
60
+ * @returns {Promise<object[]>} Loaded product catalogs (uniform shape).
61
+ * @memberof Catalog
62
+ */
63
+ const loadProductCatalogs = async () => {
64
+ const catalogs = [];
65
+ for (const file of fs.readdirSync(catalogDir)) {
66
+ if (!/^catalog-.+\.js$/.test(file) || file === 'catalog-underpost.js') continue;
67
+ try {
68
+ const mod = await import(`./${file}`);
69
+ if (mod.default) catalogs.push({ ...EMPTY_CATALOG, ...mod.default });
70
+ } catch {
71
+ /* a malformed/removed product catalog must not break the base build */
72
+ }
73
+ }
74
+ return catalogs;
75
+ };
76
+
77
+ export { loadDeployCatalog, loadProductCatalogs, EMPTY_CATALOG };
@@ -399,7 +399,7 @@ const buildCoverage = async ({ docs, docsDestination }) => {
399
399
  shellExec(`cd ${coveragePath} && npm run coverage`, { silent: true });
400
400
  } else if (pkg.scripts && pkg.scripts.test) {
401
401
  logger.info('generating coverage via test', coveragePath);
402
- shellExec(`cd ${coveragePath} && npm test`, { silent: true });
402
+ shellExec(`cd ${coveragePath} && npm test`, { silent: true, silentOnError: true });
403
403
  }
404
404
  }
405
405
  }
@@ -724,23 +724,24 @@ const buildClient = async (
724
724
  const ssrPath = path === '/' ? path : `${path}/`;
725
725
  const Render = await ssrFactory();
726
726
 
727
- if (views) {
728
- const jsSrcPath = `./src/client/sw/core.sw.js`;
729
-
730
- const jsPublicPath = `${rootClientPath}/sw.js`;
731
-
732
- if (!(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath))) {
733
- const jsSrc = await transformClientJs(jsSrcPath, {
734
- dists,
735
- proxyPath: path,
736
- baseHost,
737
- minify: minifyBuild,
738
- externalizeBareImports: false,
739
- });
740
-
741
- fs.writeFileSync(jsPublicPath, jsSrc, 'utf8');
742
- }
727
+ const swSrcPath = `./src/client/sw/core.sw.js`;
728
+ const swPublicPath = `${rootClientPath}/sw.js`;
729
+ const swShouldRebuild =
730
+ views && !(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === swSrcPath));
731
+ // Transformed SW JS is held in memory; it gets prepended with renderPayload
732
+ // and written once below, after PRE_CACHED_RESOURCES are known.
733
+ let swTransformedJs = '';
734
+ if (swShouldRebuild) {
735
+ swTransformedJs = await transformClientJs(swSrcPath, {
736
+ dists,
737
+ proxyPath: path,
738
+ baseHost,
739
+ minify: minifyBuild,
740
+ externalizeBareImports: false,
741
+ });
742
+ }
743
743
 
744
+ if (views) {
744
745
  if (
745
746
  !(
746
747
  enableLiveRebuild &&
@@ -750,9 +751,8 @@ const buildClient = async (
750
751
  )
751
752
  )
752
753
  for (const view of views) {
753
- const buildPath = `${
754
- rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
755
- }${view.path === '/' ? view.path : `${view.path}/`}`;
754
+ const buildPath = `${rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
755
+ }${view.path === '/' ? view.path : `${view.path}/`}`;
756
756
 
757
757
  if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
758
758
 
@@ -768,9 +768,8 @@ const buildClient = async (
768
768
  fs.writeFileSync(`${buildPath}${buildId}.js`, jsSrc, 'utf8');
769
769
  const title = metadata.title ? metadata.title : title;
770
770
 
771
- const canonicalURL = `https://${host}${path}${
772
- view.path === '/' ? (path === '/' ? '' : '/') : path === '/' ? `${view.path.slice(1)}/` : `${view.path}/`
773
- }`;
771
+ const canonicalURL = `https://${host}${path}${view.path === '/' ? (path === '/' ? '' : '/') : path === '/' ? `${view.path.slice(1)}/` : `${view.path}/`
772
+ }`;
774
773
 
775
774
  let ssrHeadComponents = ``;
776
775
  let ssrBodyComponents = ``;
@@ -904,12 +903,12 @@ const buildClient = async (
904
903
  `${buildPath}index.html`,
905
904
  minifyBuild
906
905
  ? await minify(htmlSrc, {
907
- minifyCSS: true,
908
- minifyJS: true,
909
- collapseBooleanAttributes: true,
910
- collapseInlineTagWhitespace: true,
911
- collapseWhitespace: true,
912
- })
906
+ minifyCSS: true,
907
+ minifyJS: true,
908
+ collapseBooleanAttributes: true,
909
+ collapseInlineTagWhitespace: true,
910
+ collapseWhitespace: true,
911
+ })
913
912
  : htmlSrc,
914
913
  'utf8',
915
914
  );
@@ -967,119 +966,85 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
967
966
  }
968
967
 
969
968
  if (client) {
970
- let PRE_CACHED_RESOURCES = [];
971
-
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
- };
984
-
985
- for (const pageType of ['offline', 'pages']) {
986
- if (confSSR[getCapVariableName(client)] && confSSR[getCapVariableName(client)][pageType]) {
987
- for (const page of confSSR[getCapVariableName(client)][pageType]) {
988
- const SsrComponent = await ssrFactory(`./src/client/ssr/${pageType}/${page.client}.js`);
989
-
990
- const htmlSrc = Render({
991
- title: page.title,
992
- ssrPath,
993
- ssrHeadComponents: '<base target="_top">',
994
- ssrBodyComponents: SsrComponent(),
995
- renderPayload: {
996
- apiBaseProxyPath,
997
- apiBaseHost,
998
- apiBasePath: process.env.BASE_API,
999
- version: Underpost.version,
1000
- ...(isDevelopment ? { dev: true } : undefined),
1001
- },
1002
- renderApi: {
1003
- JSONweb,
1004
- },
1005
- });
1006
-
1007
- const buildPath = `${
1008
- rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
1009
- }${page.path === '/' ? page.path : `${page.path}/`}`;
1010
-
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
- }
1016
-
1017
- if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
1018
-
1019
- const buildHtmlPath = `${buildPath}index.html`;
1020
-
1021
- logger.info('ssr page build', buildHtmlPath);
1022
-
1023
- fs.writeFileSync(
1024
- buildHtmlPath,
1025
- minifyBuild
1026
- ? await minify(htmlSrc, {
1027
- minifyCSS: true,
1028
- minifyJS: true,
1029
- collapseBooleanAttributes: true,
1030
- collapseInlineTagWhitespace: true,
1031
- collapseWhitespace: true,
1032
- })
1033
- : htmlSrc,
1034
- 'utf8',
1035
- );
1036
- }
1037
- }
1038
- }
969
+ const proxyPrefix = path === '/' ? '' : path;
970
+ const buildIndexUrl = (routePath) => `${proxyPrefix}${routePath === '/' ? '' : routePath}/index.html`;
971
+
972
+ // SSR views: a single declarative array. The role of each view (regular
973
+ // page vs. offline/maintenance fallback) is expressed by per-entry flags;
974
+ // fallback-flagged views are also precached so the SW can serve them
975
+ // when the network is unreachable.
976
+ const ssrClientConf = confSSR[getCapVariableName(client)] || {};
977
+ const ssrViews = Array.isArray(ssrClientConf.views) ? ssrClientConf.views : [];
978
+ const PRE_CACHED_RESOURCES = [];
979
+ let offlineFallbackUrl = null;
980
+ let maintenanceFallbackUrl = null;
981
+
982
+ for (const view of ssrViews) {
983
+ const SsrComponent = await ssrFactory(`./src/client/ssr/views/${view.client}.js`);
984
+
985
+ const htmlSrc = Render({
986
+ title: view.title,
987
+ ssrPath,
988
+ ssrHeadComponents: '<base target="_top">',
989
+ ssrBodyComponents: SsrComponent(),
990
+ renderPayload: {
991
+ apiBaseProxyPath,
992
+ apiBaseHost,
993
+ apiBasePath: process.env.BASE_API,
994
+ version: Underpost.version,
995
+ ...(isDevelopment ? { dev: true } : undefined),
996
+ },
997
+ renderApi: { JSONweb },
998
+ });
1039
999
 
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
- };
1000
+ const buildPath = `${rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
1001
+ }${view.path === '/' ? view.path : `${view.path}/`}`;
1053
1002
 
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];
1003
+ const indexUrl = buildIndexUrl(view.path);
1004
+ if (view.offlineDefault) {
1005
+ offlineFallbackUrl = indexUrl;
1006
+ PRE_CACHED_RESOURCES.push(indexUrl);
1007
+ }
1008
+ if (view.maintenanceDefault) {
1009
+ maintenanceFallbackUrl = indexUrl;
1010
+ PRE_CACHED_RESOURCES.push(indexUrl);
1011
+ }
1060
1012
 
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];
1013
+ if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
1014
+ const buildHtmlPath = `${buildPath}index.html`;
1015
+ logger.info('ssr view build', buildHtmlPath);
1067
1016
 
1068
- const offlinePath = normalizeSsrRoutePath(offlineSsrPage?.path, '/offline');
1069
- const maintenancePath = normalizeSsrRoutePath(maintenanceSsrPage?.path, '/maintenance');
1017
+ fs.writeFileSync(
1018
+ buildHtmlPath,
1019
+ minifyBuild
1020
+ ? await minify(htmlSrc, {
1021
+ minifyCSS: true,
1022
+ minifyJS: true,
1023
+ collapseBooleanAttributes: true,
1024
+ collapseInlineTagWhitespace: true,
1025
+ collapseWhitespace: true,
1026
+ })
1027
+ : htmlSrc,
1028
+ 'utf8',
1029
+ );
1030
+ }
1070
1031
 
1032
+ if (swShouldRebuild) {
1033
+ const cacheScope = path === '/' ? 'root' : path.replaceAll('/', '_');
1071
1034
  const renderPayload = {
1072
1035
  PRE_CACHED_RESOURCES: uniqueArray(PRE_CACHED_RESOURCES),
1073
1036
  PROXY_PATH: path,
1074
- CACHE_PREFIX: `engine-core-v3-${cacheScope}`,
1075
- OFFLINE_PATH: offlinePath,
1076
- MAINTENANCE_PATH: maintenancePath,
1037
+ CACHE_PREFIX: `engine-core-${cacheScope}`,
1038
+ OFFLINE_URL: offlineFallbackUrl || buildIndexUrl('/offline'),
1039
+ MAINTENANCE_URL: maintenanceFallbackUrl || buildIndexUrl('/maintenance'),
1077
1040
  };
1041
+
1042
+ // Single write: prepend the payload prelude to the transformed SW JS.
1078
1043
  fs.writeFileSync(
1079
- `${rootClientPath}/sw.js`,
1044
+ swPublicPath,
1080
1045
  `self.renderPayload = ${JSONweb(renderPayload)};
1081
1046
  self.__WB_DISABLE_DEV_LOGS = true;
1082
- ${fs.readFileSync(`${rootClientPath}/sw.js`, 'utf8')}`,
1047
+ ${swTransformedJs}`,
1083
1048
  'utf8',
1084
1049
  );
1085
1050
  }