underpost 2.8.885 → 2.81.0

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 (72) hide show
  1. package/.env.production +3 -0
  2. package/.github/workflows/ghpkg.ci.yml +1 -1
  3. package/.github/workflows/npmpkg.ci.yml +1 -1
  4. package/.github/workflows/publish.ci.yml +5 -5
  5. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -1
  6. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  7. package/.vscode/zed.keymap.json +17 -0
  8. package/.vscode/zed.settings.json +20 -0
  9. package/CHANGELOG.md +145 -1
  10. package/Dockerfile +20 -3
  11. package/README.md +6 -6
  12. package/bin/build.js +18 -9
  13. package/bin/deploy.js +130 -195
  14. package/bin/zed.js +20 -0
  15. package/cli.md +13 -7
  16. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  17. package/manifests/deployment/dd-test-development/deployment.yaml +50 -50
  18. package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
  19. package/manifests/lxd/underpost-setup.sh +5 -5
  20. package/package.json +3 -4
  21. package/{manifests/maas → scripts}/ssh-cluster-info.sh +1 -1
  22. package/scripts/ssl.sh +164 -0
  23. package/src/cli/baremetal.js +8 -8
  24. package/src/cli/cloud-init.js +1 -1
  25. package/src/cli/cluster.js +15 -4
  26. package/src/cli/cron.js +1 -1
  27. package/src/cli/db.js +2 -1
  28. package/src/cli/deploy.js +65 -14
  29. package/src/cli/fs.js +2 -2
  30. package/src/cli/image.js +19 -2
  31. package/src/cli/index.js +11 -4
  32. package/src/cli/monitor.js +34 -1
  33. package/src/cli/repository.js +42 -1
  34. package/src/cli/run.js +396 -86
  35. package/src/cli/script.js +32 -0
  36. package/src/cli/secrets.js +34 -0
  37. package/src/cli/test.js +42 -1
  38. package/src/client/components/core/Css.js +0 -8
  39. package/src/client/components/core/windowGetDimensions.js +229 -162
  40. package/src/index.js +2 -2
  41. package/src/mailer/MailerProvider.js +1 -0
  42. package/src/runtime/express/Dockerfile +41 -0
  43. package/src/runtime/express/Express.js +12 -4
  44. package/src/runtime/lampp/Dockerfile +1 -1
  45. package/src/server/backup.js +20 -0
  46. package/src/server/client-build-live.js +12 -10
  47. package/src/server/client-build.js +136 -91
  48. package/src/server/client-dev-server.js +16 -2
  49. package/src/server/client-icons.js +19 -0
  50. package/src/server/conf.js +495 -69
  51. package/src/server/dns.js +169 -46
  52. package/src/server/downloader.js +65 -24
  53. package/src/server/object-layer.js +260 -162
  54. package/src/server/peer.js +2 -8
  55. package/src/server/proxy.js +93 -76
  56. package/src/server/runtime.js +15 -16
  57. package/src/server/ssr.js +4 -4
  58. package/src/server/tls.js +251 -0
  59. package/src/server/valkey.js +11 -10
  60. package/src/ws/IoInterface.js +2 -1
  61. package/src/ws/IoServer.js +2 -1
  62. package/src/ws/core/core.ws.connection.js +1 -1
  63. package/src/ws/core/core.ws.emit.js +1 -1
  64. package/src/ws/core/core.ws.server.js +1 -1
  65. package/manifests/maas/lxd-preseed.yaml +0 -32
  66. package/src/server/ssl.js +0 -108
  67. /package/{manifests/maas → scripts}/device-scan.sh +0 -0
  68. /package/{manifests/maas → scripts}/gpu-diag.sh +0 -0
  69. /package/{manifests/maas → scripts}/maas-setup.sh +0 -0
  70. /package/{manifests/maas → scripts}/nat-iptables.sh +0 -0
  71. /package/{manifests/maas → scripts}/nvim.sh +0 -0
  72. /package/{manifests/maas → scripts}/snap-clean.sh +0 -0
@@ -0,0 +1,41 @@
1
+ FROM rockylinux:9
2
+
3
+ # --- Update and install required packages
4
+ RUN dnf -y update && \
5
+ dnf -y install epel-release && \
6
+ dnf -y install --allowerasing \
7
+ bzip2 \
8
+ sudo \
9
+ curl \
10
+ net-tools \
11
+ openssh-server \
12
+ nano \
13
+ vim-enhanced \
14
+ less \
15
+ openssl-devel \
16
+ wget \
17
+ git \
18
+ gnupg2 \
19
+ libnsl \
20
+ perl && \
21
+ dnf clean all
22
+
23
+
24
+ # Install Node.js
25
+ RUN curl -fsSL https://rpm.nodesource.com/setup_24.x | bash -
26
+ RUN dnf install nodejs -y
27
+ RUN dnf clean all
28
+
29
+ # Verify Node.js and npm versions
30
+ RUN node --version
31
+ RUN npm --version
32
+
33
+ # Set working directory
34
+ WORKDIR /home/dd
35
+
36
+ # Expose necessary ports
37
+ EXPOSE 22
38
+ EXPOSE 80
39
+ EXPOSE 443
40
+ EXPOSE 3000-3100
41
+ EXPOSE 4000-4100
@@ -21,6 +21,8 @@ import { createPeerServer } from '../../server/peer.js';
21
21
  import { createValkeyConnection } from '../../server/valkey.js';
22
22
  import { applySecurity, authMiddlewareFactory } from '../../server/auth.js';
23
23
  import { ssrMiddlewareFactory } from '../../server/ssr.js';
24
+ import { TLS } from '../../server/tls.js';
25
+ import { shellExec } from '../../server/process.js';
24
26
 
25
27
  const logger = loggerFactory(import.meta);
26
28
 
@@ -43,6 +45,7 @@ class ExpressService {
43
45
  * @param {string[]} [config.apis] - A list of API names to load and attach routers for.
44
46
  * @param {string[]} config.origins - Allowed origins for CORS.
45
47
  * @param {string} [config.directory] - The directory for static files (if overriding default).
48
+ * @param {boolean} [config.useLocalSsl] - Whether to use local SSL for the instance.
46
49
  * @param {string} [config.ws] - The WebSocket server name to use.
47
50
  * @param {object} [config.mailer] - Mailer configuration.
48
51
  * @param {object} [config.db] - Database configuration.
@@ -65,6 +68,7 @@ class ExpressService {
65
68
  apis,
66
69
  origins,
67
70
  directory,
71
+ useLocalSsl,
68
72
  ws,
69
73
  mailer,
70
74
  db,
@@ -111,7 +115,6 @@ class ExpressService {
111
115
 
112
116
  // Logging, Compression, and Body Parsers
113
117
  app.use(loggerMiddleware(import.meta));
114
- // Compression filter logic is correctly inlined here
115
118
  app.use(compression({ filter: (req, res) => !req.headers['x-no-compression'] && compression.filter(req, res) }));
116
119
  app.use(express.json({ limit: '100MB' }));
117
120
  app.use(express.urlencoded({ extended: true, limit: '20MB' }));
@@ -165,6 +168,9 @@ class ExpressService {
165
168
  }
166
169
 
167
170
  // Security and CORS
171
+ if (process.env.NODE_ENV === 'development' && useLocalSsl)
172
+ origins = origins.map((origin) => origin.replace('http', 'https'));
173
+
168
174
  applySecurity(app, {
169
175
  origin: origins,
170
176
  });
@@ -219,9 +225,7 @@ class ExpressService {
219
225
  const peerPort = newInstance(port + portsUsed); // portsUsed is 1 here
220
226
  const { options, meta, peerServer } = await createPeerServer({
221
227
  port: peerPort,
222
- devPort: port,
223
228
  origins,
224
- host,
225
229
  path,
226
230
  });
227
231
  await UnderpostStartUp.API.listenPortController(peerServer, peerPort, {
@@ -239,7 +243,11 @@ class ExpressService {
239
243
  for (const [_, ssrMiddleware] of Object.entries(ssr)) app.use(ssrMiddleware);
240
244
 
241
245
  // Start listening on the main port
242
- await UnderpostStartUp.API.listenPortController(server, port, runningData);
246
+ if (useLocalSsl && process.env.NODE_ENV === 'development') {
247
+ if (!TLS.validateSecureContext()) shellExec(`node bin/deploy tls`);
248
+ const { ServerSSL } = await TLS.createSslServer(app);
249
+ await UnderpostStartUp.API.listenPortController(ServerSSL, port, runningData);
250
+ } else await UnderpostStartUp.API.listenPortController(server, port, runningData);
243
251
 
244
252
  return { portsUsed };
245
253
  }
@@ -32,7 +32,7 @@ RUN mkdir -p /opt/lampp/htdocs && \
32
32
  chmod -R a+rX /opt/lampp/htdocs
33
33
 
34
34
  # Install Node.js
35
- RUN curl -fsSL https://rpm.nodesource.com/setup_23.x | bash -
35
+ RUN curl -fsSL https://rpm.nodesource.com/setup_24.x | bash -
36
36
  RUN dnf install nodejs -y
37
37
  RUN dnf clean all
38
38
 
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Manages backup operations for deployments.
3
+ * @module src/server/backup.js
4
+ * @namespace BackUp
5
+ */
6
+
1
7
  import fs from 'fs-extra';
2
8
  import { loggerFactory } from './logger.js';
3
9
  import { shellExec } from './process.js';
@@ -8,7 +14,21 @@ dotenv.config();
8
14
 
9
15
  const logger = loggerFactory(import.meta);
10
16
 
17
+ /**
18
+ * @class BackUp
19
+ * @description Manages backup operations for deployments.
20
+ * @memberof BackUp
21
+ */
11
22
  class BackUp {
23
+ /**
24
+ * @method callback
25
+ * @description Initiates a backup operation for the specified deployment list.
26
+ * @param {string} deployList - The list of deployments to backup.
27
+ * @param {Object} options - The options for the backup operation.
28
+ * @param {boolean} options.itc - Whether to backup inside container data.
29
+ * @param {boolean} options.git - Whether to backup data using Git.
30
+ * @memberof BackUp
31
+ */
12
32
  static callback = async function (deployList, options = { itc: false, git: false }) {
13
33
  if ((!deployList || deployList === 'dd') && fs.existsSync(`./engine-private/deploy/dd.router`))
14
34
  deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Module for live building client-side code
3
+ * @module src/server/client-build-live.js
4
+ * @namespace clientLiveBuild
5
+ */
6
+
1
7
  import fs from 'fs-extra';
2
8
  import { Config, loadConf } from './conf.js';
3
9
  import { loggerFactory } from './logger.js';
@@ -5,6 +11,11 @@ import { buildClient } from './client-build.js';
5
11
 
6
12
  const logger = loggerFactory(import.meta);
7
13
 
14
+ /**
15
+ * @function clientLiveBuild
16
+ * @description Initiates a live build of client-side code.
17
+ * @memberof clientLiveBuild
18
+ */
8
19
  const clientLiveBuild = async () => {
9
20
  if (fs.existsSync(`./tmp/client.build.json`)) {
10
21
  const deployId = process.argv[2];
@@ -33,16 +44,7 @@ const clientLiveBuild = async () => {
33
44
  ),
34
45
  );
35
46
  const confServer = JSON.parse(
36
- fs.readFileSync(
37
- fs.existsSync(`./engine-private/conf/${deployId}/conf.server.dev.${subConf}.json`)
38
- ? `./engine-private/conf/${deployId}/conf.server.dev.${subConf}.json`
39
- : fs.existsSync(`./engine-private/replica/${deployId}`)
40
- ? `./engine-private/replica/${deployId}/conf.server.json`
41
- : fs.existsSync(`./engine-private/conf/${deployId}/conf.server.json`)
42
- ? `./engine-private/conf/${deployId}/conf.server.json`
43
- : `./conf/conf.server.json`,
44
- 'utf8',
45
- ),
47
+ fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.dev.${subConf}.json`, 'utf8'),
46
48
  );
47
49
  host = process.argv[4];
48
50
  path = process.argv[5];
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Manages the client-side build process, including full builds and incremental builds.
3
+ * @module server/client-build.js
4
+ * @namespace clientBuild
5
+ */
6
+
1
7
  'use strict';
2
8
 
3
9
  import fs from 'fs-extra';
@@ -26,97 +32,17 @@ dotenv.config();
26
32
 
27
33
  // Static Site Generation (SSG)
28
34
 
29
- const buildAcmeChallengePath = (acmeChallengeFullPath = '') => {
30
- fs.mkdirSync(acmeChallengeFullPath, {
31
- recursive: true,
32
- });
33
- fs.writeFileSync(`${acmeChallengeFullPath}/.gitkeep`, '', 'utf8');
34
- };
35
-
36
- const fullBuild = async ({
37
- path,
38
- logger,
39
- client,
40
- db,
41
- dists,
42
- rootClientPath,
43
- acmeChallengeFullPath,
44
- publicClientId,
45
- iconsBuild,
46
- metadata,
47
- }) => {
48
- logger.warn('Full build', rootClientPath);
49
-
50
- buildAcmeChallengePath(acmeChallengeFullPath);
51
-
52
- if (publicClientId && publicClientId.startsWith('html-website-templates')) {
53
- if (!fs.existsSync(`/home/dd/html-website-templates/`))
54
- shellExec(`cd /home/dd && git clone https://github.com/designmodo/html-website-templates.git`);
55
- if (!fs.existsSync(`${rootClientPath}/index.php`)) {
56
- fs.copySync(`/home/dd/html-website-templates/${publicClientId.split('-publicClientId-')[1]}`, rootClientPath);
57
- shellExec(`cd ${rootClientPath} && git init && git add . && git commit -m "Base template implementation"`);
58
- // git remote add origin git@github.com:<username>/<repo>.git
59
- fs.writeFileSync(`${rootClientPath}/.git/.htaccess`, `Deny from all`, 'utf8');
60
- }
61
- return;
62
- }
63
-
64
- fs.removeSync(rootClientPath);
65
-
66
- if (fs.existsSync(`./src/client/public/${publicClientId}`)) {
67
- if (iconsBuild === true) await buildIcons({ publicClientId, metadata });
68
-
69
- fs.copySync(
70
- `./src/client/public/${publicClientId}`,
71
- rootClientPath /* {
72
- filter: function (name) {
73
- console.log(name);
74
- return true;
75
- },
76
- } */,
77
- );
78
- } else if (fs.existsSync(`./engine-private/src/client/public/${publicClientId}`)) {
79
- switch (publicClientId) {
80
- case 'mysql_test':
81
- if (db) {
82
- fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath);
83
- fs.writeFileSync(
84
- `${rootClientPath}/index.php`,
85
- fs
86
- .readFileSync(`${rootClientPath}/index.php`, 'utf8')
87
- .replace('test_servername', 'localhost')
88
- .replace('test_username', db.user)
89
- .replace('test_password', db.password)
90
- .replace('test_dbname', db.name),
91
- 'utf8',
92
- );
93
- } else logger.error('not provided db config');
94
- break;
95
-
96
- default:
97
- break;
98
- }
99
- }
100
- if (dists)
101
- for (const dist of dists) {
102
- if ('folder' in dist) {
103
- if (fs.statSync(dist.folder).isDirectory()) {
104
- fs.mkdirSync(`${rootClientPath}${dist.public_folder}`, { recursive: true });
105
- fs.copySync(dist.folder, `${rootClientPath}${dist.public_folder}`);
106
- } else {
107
- const folder = dist.public_folder.split('/');
108
- folder.pop();
109
- fs.mkdirSync(`${rootClientPath}${folder.join('/')}`, { recursive: true });
110
- fs.copyFileSync(dist.folder, `${rootClientPath}${dist.public_folder}`);
111
- }
112
- }
113
- if ('styles' in dist) {
114
- fs.mkdirSync(`${rootClientPath}${dist.public_styles_folder}`, { recursive: true });
115
- fs.copySync(dist.styles, `${rootClientPath}${dist.public_styles_folder}`);
116
- }
117
- }
118
- };
119
-
35
+ /**
36
+ * @async
37
+ * @function buildClient
38
+ * @memberof clientBuild
39
+ * @param {Object} options - Options for the build process.
40
+ * @param {Array} options.liveClientBuildPaths - List of paths to build incrementally.
41
+ * @param {Array} options.instances - List of instances to build.
42
+ * @returns {Promise<void>} - Promise that resolves when the build is complete.
43
+ * @throws {Error} - If the build fails.
44
+ * @memberof clientBuild
45
+ */
120
46
  const buildClient = async (options = { liveClientBuildPaths: [], instances: [] }) => {
121
47
  const logger = loggerFactory(import.meta);
122
48
  const confClient = JSON.parse(fs.readFileSync(`./conf/conf.client.json`, 'utf8'));
@@ -126,6 +52,125 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [] }
126
52
  const acmeChallengePath = `/.well-known/acme-challenge`;
127
53
  const publicPath = `./public`;
128
54
 
55
+ /**
56
+ * @async
57
+ * @function buildAcmeChallengePath
58
+ * @memberof clientBuild
59
+ * @param {string} acmeChallengeFullPath - Full path to the acme-challenge directory.
60
+ * @returns {void}
61
+ * @throws {Error} - If the directory cannot be created.
62
+ * @memberof clientBuild
63
+ */
64
+ const buildAcmeChallengePath = (acmeChallengeFullPath = '') => {
65
+ fs.mkdirSync(acmeChallengeFullPath, {
66
+ recursive: true,
67
+ });
68
+ fs.writeFileSync(`${acmeChallengeFullPath}/.gitkeep`, '', 'utf8');
69
+ };
70
+
71
+ /**
72
+ * @async
73
+ * @function fullBuild
74
+ * @memberof clientBuild
75
+ * @param {Object} options - Options for the full build process.
76
+ * @param {string} options.path - Path to the client directory.
77
+ * @param {Object} options.logger - Logger instance.
78
+ * @param {string} options.client - Client name.
79
+ * @param {Object} options.db - Database configuration.
80
+ * @param {Array} options.dists - List of distributions to build.
81
+ * @param {string} options.rootClientPath - Full path to the client directory.
82
+ * @param {string} options.acmeChallengeFullPath - Full path to the acme-challenge directory.
83
+ * @param {string} options.publicClientId - Public client ID.
84
+ * @param {boolean} options.iconsBuild - Whether to build icons.
85
+ * @param {Object} options.metadata - Metadata for the client.
86
+ * @returns {Promise<void>} - Promise that resolves when the full build is complete.
87
+ * @throws {Error} - If the full build fails.
88
+ * @memberof clientBuild
89
+ */
90
+ const fullBuild = async ({
91
+ path,
92
+ logger,
93
+ client,
94
+ db,
95
+ dists,
96
+ rootClientPath,
97
+ acmeChallengeFullPath,
98
+ publicClientId,
99
+ iconsBuild,
100
+ metadata,
101
+ }) => {
102
+ logger.warn('Full build', rootClientPath);
103
+
104
+ buildAcmeChallengePath(acmeChallengeFullPath);
105
+
106
+ if (publicClientId && publicClientId.startsWith('html-website-templates')) {
107
+ if (!fs.existsSync(`/home/dd/html-website-templates/`))
108
+ shellExec(`cd /home/dd && git clone https://github.com/designmodo/html-website-templates.git`);
109
+ if (!fs.existsSync(`${rootClientPath}/index.php`)) {
110
+ fs.copySync(`/home/dd/html-website-templates/${publicClientId.split('-publicClientId-')[1]}`, rootClientPath);
111
+ shellExec(`cd ${rootClientPath} && git init && git add . && git commit -m "Base template implementation"`);
112
+ // git remote add origin git@github.com:<username>/<repo>.git
113
+ fs.writeFileSync(`${rootClientPath}/.git/.htaccess`, `Deny from all`, 'utf8');
114
+ }
115
+ return;
116
+ }
117
+
118
+ fs.removeSync(rootClientPath);
119
+
120
+ if (fs.existsSync(`./src/client/public/${publicClientId}`)) {
121
+ if (iconsBuild === true) await buildIcons({ publicClientId, metadata });
122
+
123
+ fs.copySync(
124
+ `./src/client/public/${publicClientId}`,
125
+ rootClientPath /* {
126
+ filter: function (name) {
127
+ console.log(name);
128
+ return true;
129
+ },
130
+ } */,
131
+ );
132
+ } else if (fs.existsSync(`./engine-private/src/client/public/${publicClientId}`)) {
133
+ switch (publicClientId) {
134
+ case 'mysql_test':
135
+ if (db) {
136
+ fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath);
137
+ fs.writeFileSync(
138
+ `${rootClientPath}/index.php`,
139
+ fs
140
+ .readFileSync(`${rootClientPath}/index.php`, 'utf8')
141
+ .replace('test_servername', 'localhost')
142
+ .replace('test_username', db.user)
143
+ .replace('test_password', db.password)
144
+ .replace('test_dbname', db.name),
145
+ 'utf8',
146
+ );
147
+ } else logger.error('not provided db config');
148
+ break;
149
+
150
+ default:
151
+ break;
152
+ }
153
+ }
154
+ if (dists)
155
+ for (const dist of dists) {
156
+ if ('folder' in dist) {
157
+ if (fs.statSync(dist.folder).isDirectory()) {
158
+ fs.mkdirSync(`${rootClientPath}${dist.public_folder}`, { recursive: true });
159
+ fs.copySync(dist.folder, `${rootClientPath}${dist.public_folder}`);
160
+ } else {
161
+ const folder = dist.public_folder.split('/');
162
+ folder.pop();
163
+ fs.mkdirSync(`${rootClientPath}${folder.join('/')}`, { recursive: true });
164
+ fs.copyFileSync(dist.folder, `${rootClientPath}${dist.public_folder}`);
165
+ }
166
+ }
167
+ if ('styles' in dist) {
168
+ fs.mkdirSync(`${rootClientPath}${dist.public_styles_folder}`, { recursive: true });
169
+ fs.copySync(dist.styles, `${rootClientPath}${dist.public_styles_folder}`);
170
+ }
171
+ }
172
+ };
173
+
129
174
  // { srcBuildPath, publicBuildPath }
130
175
  const enableLiveRebuild =
131
176
  options && options.liveClientBuildPaths && options.liveClientBuildPaths.length > 0 ? true : false;
@@ -1,12 +1,26 @@
1
+ /**
2
+ * Module for creating a client-side development server
3
+ * @module src/server/client-dev-server.js
4
+ * @namespace clientDevServer
5
+ */
1
6
  import fs from 'fs-extra';
2
7
  import nodemon from 'nodemon';
3
8
  import { shellExec } from './process.js';
4
9
  import { loggerFactory } from './logger.js';
5
- import { writeEnv } from './conf.js';
6
- import dotenv from 'dotenv';
7
10
 
8
11
  const logger = loggerFactory(import.meta);
9
12
 
13
+ /**
14
+ * @function createClientDevServer
15
+ * @description Creates a client-side development server.
16
+ * @memberof clientDevServer
17
+ * @param {string} deployId - The deployment ID.
18
+ * @param {string} subConf - The sub-configuration.
19
+ * @param {string} host - The host.
20
+ * @param {string} path - The path.
21
+ * @returns {void}
22
+ * @memberof clientDevServer
23
+ */
10
24
  const createClientDevServer = (
11
25
  deployId = process.argv[2] || 'dd-default',
12
26
  subConf = process.argv[3] || '',
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Module for building client-side icons
3
+ * @module src/server/client-icons.js
4
+ * @namespace clientIcons
5
+ */
1
6
  import { favicons } from 'favicons';
2
7
  import { loggerFactory } from './logger.js';
3
8
  import fs from 'fs-extra';
@@ -5,6 +10,20 @@ import { getCapVariableName } from '../client/components/core/CommonJs.js';
5
10
 
6
11
  const logger = loggerFactory(import.meta);
7
12
 
13
+ /**
14
+ * @function buildIcons
15
+ * @description Builds icons for a client-side application.
16
+ * @memberof clientIcons
17
+ * @param {Object} metadata - The metadata for the client-side application.
18
+ * @param {string} metadata.title - The title of the client-side application.
19
+ * @param {string} metadata.description - The description of the client-side application.
20
+ * @param {string} metadata.keywords - The keywords for the client-side application.
21
+ * @param {string} metadata.author - The author of the client-side application.
22
+ * @param {string} metadata.thumbnail - The thumbnail of the client-side application.
23
+ * @param {string} metadata.themeColor - The theme color of the client-side application.
24
+ * @param {string} metadata.baseBuildIconReference - The base build icon reference for the client-side application.
25
+ * @returns {Promise<void>}
26
+ */
8
27
  const buildIcons = async ({
9
28
  publicClientId,
10
29
  metadata: { title, description, keywords, author, thumbnail, themeColor, baseBuildIconReference },