@underpostnet/underpost 3.0.3 → 3.1.1

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 (82) hide show
  1. package/{.env.production → .env.example} +20 -2
  2. package/.github/workflows/ghpkg.ci.yml +1 -1
  3. package/.github/workflows/gitlab.ci.yml +1 -1
  4. package/.github/workflows/npmpkg.ci.yml +22 -7
  5. package/.github/workflows/publish.ci.yml +5 -5
  6. package/.github/workflows/pwa-microservices-template-page.cd.yml +3 -3
  7. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  8. package/.github/workflows/release.cd.yml +3 -2
  9. package/.vscode/extensions.json +9 -8
  10. package/.vscode/settings.json +3 -2
  11. package/CHANGELOG.md +160 -1
  12. package/CLI-HELP.md +71 -52
  13. package/README.md +2 -2
  14. package/bin/build.js +4 -1
  15. package/bin/deploy.js +150 -208
  16. package/bin/file.js +2 -1
  17. package/bin/vs.js +3 -3
  18. package/conf.js +30 -13
  19. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  20. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  21. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  22. package/manifests/deployment/dd-test-development/deployment.yaml +52 -52
  23. package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
  24. package/manifests/pv-pvc-dd.yaml +1 -1
  25. package/package.json +48 -43
  26. package/scripts/k3s-node-setup.sh +1 -1
  27. package/src/api/document/document.service.js +1 -1
  28. package/src/api/file/file.controller.js +3 -1
  29. package/src/api/file/file.service.js +28 -5
  30. package/src/api/user/user.router.js +10 -5
  31. package/src/api/user/user.service.js +7 -7
  32. package/src/cli/baremetal.js +6 -10
  33. package/src/cli/cloud-init.js +0 -3
  34. package/src/cli/db.js +54 -71
  35. package/src/cli/deploy.js +64 -12
  36. package/src/cli/env.js +5 -5
  37. package/src/cli/fs.js +0 -2
  38. package/src/cli/image.js +0 -3
  39. package/src/cli/index.js +27 -13
  40. package/src/cli/monitor.js +5 -6
  41. package/src/cli/repository.js +329 -35
  42. package/src/cli/run.js +118 -69
  43. package/src/cli/secrets.js +1 -3
  44. package/src/cli/ssh.js +1 -1
  45. package/src/client/components/core/AgGrid.js +20 -5
  46. package/src/client/components/core/Content.js +22 -3
  47. package/src/client/components/core/Docs.js +21 -4
  48. package/src/client/components/core/FileExplorer.js +71 -4
  49. package/src/client/components/core/Input.js +1 -1
  50. package/src/client/components/core/Modal.js +20 -6
  51. package/src/client/public/default/sitemap +3 -3
  52. package/src/client/public/test/sitemap +3 -3
  53. package/src/client.build.js +0 -3
  54. package/src/client.dev.js +0 -3
  55. package/src/db/DataBaseProvider.js +17 -2
  56. package/src/db/mariadb/MariaDB.js +14 -9
  57. package/src/db/mongo/MongooseDB.js +17 -1
  58. package/src/index.js +1 -1
  59. package/src/proxy.js +0 -3
  60. package/src/runtime/express/Express.js +7 -1
  61. package/src/runtime/lampp/Lampp.js +6 -13
  62. package/src/server/auth.js +6 -9
  63. package/src/server/backup.js +2 -3
  64. package/src/server/client-build-docs.js +178 -3
  65. package/src/server/client-build-live.js +9 -18
  66. package/src/server/client-build.js +175 -38
  67. package/src/server/client-dev-server.js +14 -13
  68. package/src/server/conf.js +357 -149
  69. package/src/server/cron.js +2 -1
  70. package/src/server/dns.js +28 -12
  71. package/src/server/downloader.js +0 -2
  72. package/src/server/logger.js +27 -9
  73. package/src/server/peer.js +0 -2
  74. package/src/server/process.js +1 -50
  75. package/src/server/proxy.js +4 -8
  76. package/src/server/runtime.js +5 -8
  77. package/src/server/ssr.js +0 -3
  78. package/src/server/start.js +5 -5
  79. package/src/server/tls.js +0 -2
  80. package/src/server.js +0 -4
  81. package/.env.development +0 -43
  82. package/.env.test +0 -43
@@ -59,6 +59,10 @@ const buildApiDocs = async ({
59
59
  name: 'user',
60
60
  description: 'User API operations',
61
61
  },
62
+ {
63
+ name: 'object-layer',
64
+ description: 'Object Layer API operations',
65
+ },
62
66
  ],
63
67
  components: {
64
68
  schemas: {
@@ -150,6 +154,70 @@ const buildApiDocs = async ({
150
154
  },
151
155
  },
152
156
  },
157
+ objectLayerResponse: {
158
+ type: 'object',
159
+ properties: {
160
+ status: { type: 'string', example: 'success' },
161
+ data: {
162
+ type: 'object',
163
+ properties: {
164
+ _id: { type: 'string', example: '66c377f57f99e5969b81de89' },
165
+ data: {
166
+ type: 'object',
167
+ properties: {
168
+ stats: {
169
+ type: 'object',
170
+ properties: {
171
+ effect: { type: 'number', example: 0 },
172
+ resistance: { type: 'number', example: 0 },
173
+ agility: { type: 'number', example: 0 },
174
+ range: { type: 'number', example: 0 },
175
+ intelligence: { type: 'number', example: 0 },
176
+ utility: { type: 'number', example: 0 },
177
+ },
178
+ },
179
+ item: {
180
+ type: 'object',
181
+ properties: {
182
+ id: { type: 'string', example: 'skin-default' },
183
+ type: { type: 'string', example: 'skin' },
184
+ description: { type: 'string', example: 'Default skin layer' },
185
+ activable: { type: 'boolean', example: false },
186
+ },
187
+ },
188
+ ledger: {
189
+ type: 'object',
190
+ properties: {
191
+ type: { type: 'string', example: 'semi-fungible' },
192
+ address: { type: 'string', example: '0x0000000000000000000000000000000000000000' },
193
+ tokenId: { type: 'string', example: '' },
194
+ },
195
+ },
196
+ render: {
197
+ type: 'object',
198
+ properties: {
199
+ cid: { type: 'string', example: '' },
200
+ metadataCid: { type: 'string', example: '' },
201
+ },
202
+ },
203
+ },
204
+ },
205
+ cid: { type: 'string', example: '' },
206
+ sha256: { type: 'string', example: 'abc123def456...' },
207
+ },
208
+ },
209
+ },
210
+ },
211
+ objectLayerBadRequestResponse: {
212
+ type: 'object',
213
+ properties: {
214
+ status: { type: 'string', example: 'error' },
215
+ message: {
216
+ type: 'string',
217
+ example: 'Bad request. Please check your inputs, and try again',
218
+ },
219
+ },
220
+ },
153
221
  securitySchemes: {
154
222
  bearerAuth: {
155
223
  type: 'http',
@@ -221,7 +289,7 @@ const buildApiDocs = async ({
221
289
  const outputFile = `./public/${host}${path === '/' ? path : `${path}/`}swagger-output.json`;
222
290
  const routes = [];
223
291
  for (const api of apis) {
224
- if (['user'].includes(api)) routes.push(`./src/api/${api}/${api}.router.js`);
292
+ if (['user', 'object-layer'].includes(api)) routes.push(`./src/api/${api}/${api}.router.js`);
225
293
  }
226
294
 
227
295
  await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
@@ -267,14 +335,82 @@ const buildApiDocs = async ({
267
335
  */
268
336
  const buildJsDocs = async ({ host, path, metadata = {}, publicClientId }) => {
269
337
  const logger = loggerFactory(import.meta);
270
- const jsDocsConfig = JSON.parse(fs.readFileSync(`./jsdoc.json`, 'utf8'));
338
+
339
+ // Detect custom jsdoc.<deployId>.json by matching host against deploy server configs
340
+ let customJsDocPath = '';
341
+ const privateConfBase = `./engine-private/conf`;
342
+ if (fs.existsSync(privateConfBase)) {
343
+ for (const deployId of fs.readdirSync(privateConfBase)) {
344
+ const candidatePath = `./jsdoc.${deployId}.json`;
345
+ if (!fs.existsSync(candidatePath)) continue;
346
+
347
+ // Check if this deployId's server config contains the current host
348
+ const serverConfPath = `${privateConfBase}/${deployId}/conf.server.json`;
349
+ if (fs.existsSync(serverConfPath)) {
350
+ try {
351
+ const serverConf = JSON.parse(fs.readFileSync(serverConfPath, 'utf8'));
352
+ if (serverConf[host]) {
353
+ customJsDocPath = candidatePath;
354
+ logger.info('detected custom jsdoc config', { deployId, host, path: candidatePath });
355
+ break;
356
+ }
357
+ } catch (e) {
358
+ // skip invalid JSON
359
+ }
360
+ }
361
+
362
+ // Fallback: also check dev server configs
363
+ if (!customJsDocPath) {
364
+ const devConfFiles = fs
365
+ .readdirSync(`${privateConfBase}/${deployId}`)
366
+ .filter((f) => f.match(/^conf\.server\.dev\..*\.json$/));
367
+ for (const devFile of devConfFiles) {
368
+ try {
369
+ const devConf = JSON.parse(fs.readFileSync(`${privateConfBase}/${deployId}/${devFile}`, 'utf8'));
370
+ if (devConf[host]) {
371
+ customJsDocPath = candidatePath;
372
+ logger.info('detected custom jsdoc config (dev)', { deployId, host, path: candidatePath });
373
+ break;
374
+ }
375
+ } catch (e) {
376
+ // skip invalid JSON
377
+ }
378
+ }
379
+ }
380
+ if (customJsDocPath) break;
381
+ }
382
+ }
383
+
384
+ const jsDocSourcePath = customJsDocPath || `./jsdoc.json`;
385
+ const jsDocsConfig = JSON.parse(fs.readFileSync(jsDocSourcePath, 'utf8'));
386
+ logger.info('using jsdoc config', jsDocSourcePath);
271
387
 
272
388
  jsDocsConfig.opts.destination = `./public/${host}${path === '/' ? path : `${path}/`}docs/`;
273
389
  jsDocsConfig.opts.theme_opts.title = metadata?.title ? metadata.title : undefined;
274
390
  jsDocsConfig.opts.theme_opts.favicon = `./public/${host}${path === '/' ? '/' : `${path}/`}favicon.ico`;
275
391
 
276
392
  const tutorialsPath = `./src/client/public/${publicClientId}/docs/references`;
277
- if (fs.existsSync(tutorialsPath)) {
393
+
394
+ // Auto-prepare hardhat references when jsdoc config includes hardhat source files
395
+ const includesHardhat =
396
+ jsDocsConfig.source &&
397
+ Array.isArray(jsDocsConfig.source.include) &&
398
+ jsDocsConfig.source.include.some((p) => p.includes('hardhat/'));
399
+ if (includesHardhat && fs.existsSync(`./hardhat`)) {
400
+ fs.mkdirSync(tutorialsPath, { recursive: true });
401
+ const hardhatReadmePath = `./hardhat/README.md`;
402
+ const hardhatWhitePaperPath = `./hardhat/WHITE-PAPER.md`;
403
+ if (fs.existsSync(hardhatReadmePath)) {
404
+ fs.copySync(hardhatReadmePath, `${tutorialsPath}/Hardhat Module.md`);
405
+ logger.info('copied hardhat README.md to tutorials references');
406
+ }
407
+ if (fs.existsSync(hardhatWhitePaperPath)) {
408
+ fs.copySync(hardhatWhitePaperPath, `${tutorialsPath}/White Paper.md`);
409
+ logger.info('copied hardhat WHITE-PAPER.md to tutorials references');
410
+ }
411
+ }
412
+
413
+ if (fs.existsSync(tutorialsPath) && fs.readdirSync(tutorialsPath).length > 0) {
278
414
  jsDocsConfig.opts.tutorials = tutorialsPath;
279
415
  if (jsDocsConfig.opts.theme_opts.sections && !jsDocsConfig.opts.theme_opts.sections.includes('Tutorials')) {
280
416
  jsDocsConfig.opts.theme_opts.sections.push('Tutorials');
@@ -311,6 +447,45 @@ const buildCoverage = async ({ host, path }) => {
311
447
  fs.copySync(`./coverage`, coverageBuildPath);
312
448
 
313
449
  logger.warn('build coverage', coverageBuildPath);
450
+
451
+ // Include hardhat coverage for cyberia-related builds
452
+ const hardhatCoveragePath = `./hardhat/coverage`;
453
+ if (fs.existsSync(hardhatCoveragePath) && fs.readdirSync(hardhatCoveragePath).length > 0) {
454
+ const hardhatCoverageBuildPath = `${jsDocsConfig.opts.destination}hardhat-coverage`;
455
+ fs.mkdirSync(hardhatCoverageBuildPath, { recursive: true });
456
+ fs.copySync(hardhatCoveragePath, hardhatCoverageBuildPath);
457
+ logger.warn('build hardhat coverage', hardhatCoverageBuildPath);
458
+ } else if (fs.existsSync(`./hardhat/package.json`)) {
459
+ // Attempt to generate hardhat coverage if the hardhat project exists
460
+ try {
461
+ const hardhatPkg = JSON.parse(fs.readFileSync(`./hardhat/package.json`, 'utf8'));
462
+ if (hardhatPkg.scripts && hardhatPkg.scripts.coverage) {
463
+ logger.info('generating hardhat coverage report');
464
+ shellExec(`cd ./hardhat && npx hardhat coverage`, { silent: true });
465
+ if (fs.existsSync(hardhatCoveragePath) && fs.readdirSync(hardhatCoveragePath).length > 0) {
466
+ const hardhatCoverageBuildPath = `${jsDocsConfig.opts.destination}hardhat-coverage`;
467
+ fs.mkdirSync(hardhatCoverageBuildPath, { recursive: true });
468
+ fs.copySync(hardhatCoveragePath, hardhatCoverageBuildPath);
469
+ logger.warn('build hardhat coverage (generated)', hardhatCoverageBuildPath);
470
+ }
471
+ }
472
+ } catch (e) {
473
+ logger.warn('hardhat coverage generation skipped', e.message);
474
+ }
475
+ }
476
+
477
+ // Copy hardhat README and WHITE-PAPER as markdown docs
478
+ const docsDestBase = jsDocsConfig.opts.destination;
479
+ const hardhatReadmePath = `./hardhat/README.md`;
480
+ const hardhatWhitePaperPath = `./hardhat/WHITE-PAPER.md`;
481
+ if (fs.existsSync(hardhatReadmePath)) {
482
+ fs.copySync(hardhatReadmePath, `${docsDestBase}hardhat-README.md`);
483
+ logger.info('copied hardhat README.md to docs');
484
+ }
485
+ if (fs.existsSync(hardhatWhitePaperPath)) {
486
+ fs.copySync(hardhatWhitePaperPath, `${docsDestBase}hardhat-WHITE-PAPER.md`);
487
+ logger.info('copied hardhat WHITE-PAPER.md to docs');
488
+ }
314
489
  };
315
490
 
316
491
  /**
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import fs from 'fs-extra';
8
- import { Config, loadConf } from './conf.js';
8
+ import { Config, loadConf, readConfJson } from './conf.js';
9
9
  import { loggerFactory } from './logger.js';
10
10
  import { buildClient } from './client-build.js';
11
11
 
@@ -17,14 +17,14 @@ const logger = loggerFactory(import.meta);
17
17
  * @memberof clientLiveBuild
18
18
  */
19
19
  const clientLiveBuild = async () => {
20
- if (fs.existsSync(`./tmp/client.build.json`)) {
20
+ if (fs.existsSync(`/tmp/client.build.json`)) {
21
21
  const deployId = process.argv[2];
22
22
  const subConf = process.argv[3];
23
23
  let clientId = 'default';
24
24
  let host = 'default.net';
25
25
  let path = '/';
26
26
  let baseHost = `${host}${path === '/' ? '' : path}`;
27
- let views = Config.default.client[clientId].views;
27
+ let views;
28
28
  let apiBaseHost;
29
29
  let apiBaseProxyPath;
30
30
 
@@ -33,19 +33,8 @@ const clientLiveBuild = async () => {
33
33
  (fs.existsSync(`./engine-private/conf/${deployId}`) || fs.existsSync(`./engine-private/replica/${deployId}`))
34
34
  ) {
35
35
  loadConf(deployId, subConf);
36
- const confClient = JSON.parse(
37
- fs.readFileSync(
38
- fs.existsSync(`./engine-private/replica/${deployId}`)
39
- ? `./engine-private/replica/${deployId}/conf.client.json`
40
- : fs.existsSync(`./engine-private/conf/${deployId}/conf.client.json`)
41
- ? `./engine-private/conf/${deployId}/conf.client.json`
42
- : `./conf/conf.client.json`,
43
- 'utf8',
44
- ),
45
- );
46
- const confServer = JSON.parse(
47
- fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.dev.${subConf}.json`, 'utf8'),
48
- );
36
+ const confClient = readConfJson(deployId, 'client');
37
+ const confServer = readConfJson(deployId, 'server');
49
38
  host = process.argv[4];
50
39
  path = process.argv[5];
51
40
  clientId = confServer[host][path].client;
@@ -53,6 +42,8 @@ const clientLiveBuild = async () => {
53
42
  baseHost = `${host}${path === '/' ? '' : path}`;
54
43
  apiBaseHost = confServer[host][path].apiBaseHost;
55
44
  apiBaseProxyPath = confServer[host][path].apiBaseProxyPath;
45
+ } else {
46
+ views = Config.default.client[clientId].views;
56
47
  }
57
48
 
58
49
  logger.info('Live build config', {
@@ -67,7 +58,7 @@ const clientLiveBuild = async () => {
67
58
  apiBaseProxyPath,
68
59
  });
69
60
 
70
- const updates = JSON.parse(fs.readFileSync(`./tmp/client.build.json`, 'utf8'));
61
+ const updates = JSON.parse(fs.readFileSync(`/tmp/client.build.json`, 'utf8'));
71
62
  const liveClientBuildPaths = [];
72
63
  for (let srcPath of updates) {
73
64
  srcPath = srcPath.replaceAll('/', `\\`);
@@ -102,7 +93,7 @@ const clientLiveBuild = async () => {
102
93
  }
103
94
  logger.info('liveClientBuildPaths', liveClientBuildPaths);
104
95
  await buildClient({ liveClientBuildPaths, instances: [{ host, path }] });
105
- fs.removeSync(`./tmp/client.build.json`);
96
+ fs.removeSync(`/tmp/client.build.json`);
106
97
  }
107
98
  };
108
99
 
@@ -15,9 +15,9 @@ import {
15
15
  orderArrayFromAttrInt,
16
16
  uniqueArray,
17
17
  } from '../client/components/core/CommonJs.js';
18
+ import { readConfJson } from './conf.js';
18
19
  import UglifyJS from 'uglify-js';
19
20
  import { minify } from 'html-minifier-terser';
20
- import dotenv from 'dotenv';
21
21
  import AdmZip from 'adm-zip';
22
22
  import * as dir from 'path';
23
23
  import { shellExec } from './process.js';
@@ -28,8 +28,6 @@ import Underpost from '../index.js';
28
28
  import { buildDocs } from './client-build-docs.js';
29
29
  import { ssrFactory } from './ssr.js';
30
30
 
31
- dotenv.config();
32
-
33
31
  // Static Site Generation (SSG)
34
32
 
35
33
  /**
@@ -77,6 +75,156 @@ const copyNonExistingFiles = (src, dest) => {
77
75
  }
78
76
  };
79
77
 
78
+ /** @type {string} Default XSL sitemap template used when no `sitemap` source file exists in the public directory. */
79
+ const defaultSitemapXsl = `<?xml version="1.0" encoding="UTF-8"?>
80
+ <xsl:stylesheet version="1.0"
81
+ xmlns:html="http://www.w3.org/TR/REC-html40"
82
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
83
+ xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
84
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
85
+ <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
86
+ <xsl:template match="/">
87
+ <html xmlns="http://www.w3.org/1999/xhtml">
88
+ <head>
89
+ <title>XML Sitemap</title>
90
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
91
+ <style type="text/css">
92
+ body {
93
+ font-family: sans-serif;
94
+ font-size: 16px;
95
+ color: #242628;
96
+ }
97
+ a {
98
+ color: #000;
99
+ text-decoration: none;
100
+ }
101
+ a:hover {
102
+ text-decoration: underline;
103
+ }
104
+ table {
105
+ border: none;
106
+ border-collapse: collapse;
107
+ width: 100%
108
+ }
109
+ th {
110
+ text-align: left;
111
+ padding-right: 30px;
112
+ font-size: 11px;
113
+ }
114
+ thead th {
115
+ border-bottom: 1px solid #7d878a;
116
+ cursor: pointer;
117
+ }
118
+ td {
119
+ font-size:11px;
120
+ padding: 5px;
121
+ }
122
+ tr:nth-child(odd) td {
123
+ background-color: rgba(0,0,0,0.04);
124
+ }
125
+ tr:hover td {
126
+ background-color: #e2edf2;
127
+ }
128
+
129
+ #content {
130
+ margin: 0 auto;
131
+ padding: 2% 5%;
132
+ max-width: 800px;
133
+ }
134
+
135
+ .desc {
136
+ margin: 18px 3px;
137
+ line-height: 1.2em;
138
+ }
139
+ .desc a {
140
+ color: #5ba4e5;
141
+ }
142
+ </style>
143
+ </head>
144
+ <body>
145
+ <div id="content">
146
+ <h1>XML Sitemap</h1>
147
+ <p class="desc"> This is a sitemap generated by <a
148
+ href="{{web-url}}">{{web-url}}</a>
149
+ </p>
150
+ <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &gt; 0">
151
+ <table id="sitemap" cellpadding="3">
152
+ <thead>
153
+ <tr>
154
+ <th width="75%">Sitemap</th>
155
+ <th width="25%">Last Modified</th>
156
+ </tr>
157
+ </thead>
158
+ <tbody>
159
+ <xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
160
+ <xsl:variable name="sitemapURL">
161
+ <xsl:value-of select="sitemap:loc" />
162
+ </xsl:variable>
163
+ <tr>
164
+ <td>
165
+ <a href="{$sitemapURL}">
166
+ <xsl:value-of select="sitemap:loc" />
167
+ </a>
168
+ </td>
169
+ <td>
170
+ <xsl:value-of
171
+ select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)))" />
172
+ </td>
173
+ </tr>
174
+ </xsl:for-each>
175
+ </tbody>
176
+ </table>
177
+ </xsl:if>
178
+ <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &lt; 1">
179
+ <p class="desc">
180
+ <a href="{{web-url}}sitemap.xml" class="back-link">&#8592; Back to index</a>
181
+ </p>
182
+ <table
183
+ id="sitemap" cellpadding="3">
184
+ <thead>
185
+ <tr>
186
+ <th width="70%">URL (<xsl:value-of
187
+ select="count(sitemap:urlset/sitemap:url)" /> total)</th>
188
+ <th width="15%">Images</th>
189
+ <th title="Last Modification Time" width="15%">Last Modified</th>
190
+ </tr>
191
+ </thead>
192
+ <tbody>
193
+ <xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'" />
194
+ <xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
195
+ <xsl:for-each select="sitemap:urlset/sitemap:url">
196
+ <tr>
197
+ <td>
198
+ <xsl:variable name="itemURL">
199
+ <xsl:value-of select="sitemap:loc" />
200
+ </xsl:variable>
201
+ <a href="{$itemURL}">
202
+ <xsl:value-of select="sitemap:loc" />
203
+ </a>
204
+ </td>
205
+ <td>
206
+ <xsl:value-of select="count(image:image)" />
207
+ </td>
208
+ <td>
209
+ <xsl:value-of
210
+ select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)))" />
211
+ </td>
212
+ </tr>
213
+ </xsl:for-each>
214
+ </tbody>
215
+ </table>
216
+ <p
217
+ class="desc">
218
+ <a href="{{web-url}}sitemap.xml" class="back-link">&#8592; Back to index</a>
219
+ </p>
220
+ </xsl:if>
221
+ </div>
222
+ </body>
223
+ </html>
224
+
225
+ </xsl:template>
226
+ </xsl:stylesheet>`;
227
+
80
228
  /**
81
229
  * @async
82
230
  * @function buildClient
@@ -91,9 +239,10 @@ const copyNonExistingFiles = (src, dest) => {
91
239
  */
92
240
  const buildClient = async (options = { liveClientBuildPaths: [], instances: [], buildZip: false }) => {
93
241
  const logger = loggerFactory(import.meta);
94
- const confClient = JSON.parse(fs.readFileSync(`./conf/conf.client.json`, 'utf8'));
95
- const confServer = JSON.parse(fs.readFileSync(`./conf/conf.server.json`, 'utf8'));
96
- const confSSR = JSON.parse(fs.readFileSync(`./conf/conf.ssr.json`, 'utf8'));
242
+ const deployId = process.env.DEPLOY_ID;
243
+ const confClient = readConfJson(deployId, 'client');
244
+ const confServer = readConfJson(deployId, 'server', { loadReplicas: true });
245
+ const confSSR = readConfJson(deployId, 'ssr');
97
246
  const packageData = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
98
247
  const acmeChallengePath = `/.well-known/acme-challenge`;
99
248
  const publicPath = `./public`;
@@ -178,26 +327,7 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
178
327
  } */,
179
328
  );
180
329
  } else if (fs.existsSync(`./engine-private/src/client/public/${publicClientId}`)) {
181
- switch (publicClientId) {
182
- case 'mysql_test':
183
- if (db) {
184
- fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath);
185
- fs.writeFileSync(
186
- `${rootClientPath}/index.php`,
187
- fs
188
- .readFileSync(`${rootClientPath}/index.php`, 'utf8')
189
- .replace('test_servername', 'localhost')
190
- .replace('test_username', db.user)
191
- .replace('test_password', db.password)
192
- .replace('test_dbname', db.name),
193
- 'utf8',
194
- );
195
- } else logger.error('not provided db config');
196
- break;
197
-
198
- default:
199
- break;
200
- }
330
+ fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath);
201
331
  }
202
332
  if (dists)
203
333
  for (const dist of dists) {
@@ -573,31 +703,38 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
573
703
  }
574
704
  }
575
705
  if (!enableLiveRebuild && siteMapLinks.length > 0) {
576
- const xslUrl = fs.existsSync(`${rootClientPath}/sitemap`)
577
- ? `${path === '/' ? '' : path}/sitemap.xsl`
578
- : undefined;
579
- // Create a stream to write to
706
+ const hasSitemapTemplate = fs.existsSync(`${rootClientPath}/sitemap`);
707
+ const sitemapBaseUrl = `https://${host}${path === '/' ? '' : path}`;
708
+ // Create a stream to write to — omit xslUrl so we can inject a relative href below
580
709
  /** @type {import('sitemap').SitemapStreamOptions} */
581
- const sitemapOptions = { hostname: `https://${host}`, xslUrl };
710
+ const sitemapOptions = { hostname: `https://${host}` };
582
711
 
583
712
  const siteMapStream = new SitemapStream(sitemapOptions);
584
713
  let siteMapSrc = await new Promise((resolve) =>
585
714
  streamToPromise(Readable.from(siteMapLinks).pipe(siteMapStream)).then((data) => resolve(data.toString())),
586
715
  );
587
716
 
717
+ // Inject a relative xml-stylesheet PI so the XSL loads from the same origin
718
+ // (works on both http://localhost:<port> and https://production-host)
719
+ siteMapSrc = siteMapSrc.replace(
720
+ '<?xml version="1.0" encoding="UTF-8"?>',
721
+ '<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="sitemap.xsl"?>',
722
+ );
723
+
588
724
  // Return a promise that resolves with your XML string
589
725
  fs.writeFileSync(`${rootClientPath}/sitemap.xml`, siteMapSrc, 'utf8');
590
- if (xslUrl)
591
- fs.writeFileSync(
592
- `${rootClientPath}/sitemap.xsl`,
593
- fs.readFileSync(`${rootClientPath}/sitemap`, 'utf8').replaceAll('{{web-url}}', `https://${host}${path}`),
594
- 'utf8',
595
- );
726
+
727
+ // Generate XSL stylesheet from source template or default fallback
728
+ const xslTemplate = hasSitemapTemplate
729
+ ? fs.readFileSync(`${rootClientPath}/sitemap`, 'utf8')
730
+ : defaultSitemapXsl;
731
+ const webUrl = `https://${host}${path === '/' ? '/' : `${path}/`}`;
732
+ fs.writeFileSync(`${rootClientPath}/sitemap.xsl`, xslTemplate.replaceAll('{{web-url}}', webUrl), 'utf8');
596
733
 
597
734
  fs.writeFileSync(
598
735
  `${rootClientPath}/robots.txt`,
599
736
  `User-agent: *
600
- Sitemap: https://${host}${path === '/' ? '' : path}/sitemap.xml`,
737
+ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
601
738
  'utf8',
602
739
  );
603
740
  }
@@ -5,8 +5,10 @@
5
5
  */
6
6
  import fs from 'fs-extra';
7
7
  import nodemon from 'nodemon';
8
+ import dotenv from 'dotenv';
8
9
  import { shellExec } from './process.js';
9
10
  import { loggerFactory } from './logger.js';
11
+ import Underpost from '../index.js';
10
12
 
11
13
  const logger = loggerFactory(import.meta);
12
14
 
@@ -21,22 +23,20 @@ const logger = loggerFactory(import.meta);
21
23
  * @returns {void}
22
24
  * @memberof clientDevServer
23
25
  */
24
- const createClientDevServer = (
26
+ const createClientDevServer = async (
25
27
  deployId = process.argv[2] || 'dd-default',
26
28
  subConf = process.argv[3] || '',
27
29
  host = process.argv[4] || 'default.net',
28
30
  path = process.argv[5] || '/',
29
31
  ) => {
30
- shellExec(
31
- `env-cmd -f ./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${subConf}-dev-client node bin/deploy build-full-client ${deployId} ${subConf}-dev-client ${host} ${path}`.trim(),
32
- );
32
+ const devClientEnvPath = `./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${subConf}-dev-client`;
33
+ if (fs.existsSync(devClientEnvPath)) dotenv.config({ path: devClientEnvPath, override: true });
33
34
 
34
- shellExec(
35
- `env-cmd -f ./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${subConf}-dev-client node src/server ${deployId} ${subConf}-dev-client`.trim(),
36
- {
37
- async: true,
38
- },
39
- );
35
+ await Underpost.repo.client(deployId, `${subConf}-dev-client`.trim(), host, path);
36
+
37
+ shellExec(`node src/server ${deployId} ${subConf}-dev-client`.trim(), {
38
+ async: true,
39
+ });
40
40
 
41
41
  // https://github.com/remy/nodemon/blob/main/doc/events.md
42
42
 
@@ -47,7 +47,7 @@ const createClientDevServer = (
47
47
  // restart([ array of files triggering the restart ]) - child process has restarted
48
48
  // config:update - nodemon's config has changed
49
49
 
50
- if (fs.existsSync(`./tmp/client.build.json`)) fs.removeSync(`./tmp/client.build.json`);
50
+ if (fs.existsSync(`/tmp/client.build.json`)) fs.removeSync(`/tmp/client.build.json`);
51
51
 
52
52
  let buildPathScope = [];
53
53
 
@@ -77,10 +77,11 @@ const createClientDevServer = (
77
77
  }, 2500);
78
78
  const buildPathScopeBuild = buildPathScope.map((o) => o.path);
79
79
  logger.info('buildPathScopeBuild', buildPathScopeBuild);
80
- fs.writeFileSync(`./tmp/client.build.json`, JSON.stringify(buildPathScopeBuild, null, 4));
80
+ fs.writeFileSync(`/tmp/client.build.json`, JSON.stringify(buildPathScopeBuild, null, 4));
81
81
  })
82
82
  .on('crash', function (error) {
83
- logger.error(error, error.message);
83
+ if (error) logger.error(error, error.message || 'nodemon crash');
84
+ else logger.error('nodemon process crashed');
84
85
  });
85
86
  };
86
87