underpost 2.7.1 → 2.7.3

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 (214) hide show
  1. package/.dockerignore +13 -13
  2. package/.env.development +7 -7
  3. package/.env.production +7 -7
  4. package/.env.test +7 -7
  5. package/.github/workflows/publish.yml +26 -0
  6. package/.github/workflows/test.yml +80 -0
  7. package/.nycrc +9 -9
  8. package/.prettierignore +12 -12
  9. package/.prettierrc +9 -9
  10. package/.vscode/extensions.json +72 -72
  11. package/.vscode/settings.json +100 -99
  12. package/Dockerfile +89 -89
  13. package/LICENSE +21 -21
  14. package/README.md +96 -96
  15. package/bin/db.js +172 -119
  16. package/bin/deploy.js +607 -661
  17. package/bin/file.js +93 -92
  18. package/bin/index.js +76 -53
  19. package/bin/ssl.js +55 -64
  20. package/bin/util.js +182 -182
  21. package/bin/vs.js +35 -35
  22. package/conf.js +251 -249
  23. package/docker-compose.yml +67 -67
  24. package/jsconfig.json +7 -7
  25. package/jsdoc.json +32 -32
  26. package/nodemon.json +6 -6
  27. package/package.json +137 -132
  28. package/prometheus.yml +36 -36
  29. package/setup.sh +24 -24
  30. package/src/api/core/core.controller.js +69 -69
  31. package/src/api/core/core.model.js +11 -11
  32. package/src/api/core/core.router.js +23 -23
  33. package/src/api/core/core.service.js +29 -29
  34. package/src/api/crypto/crypto.controller.js +51 -51
  35. package/src/api/crypto/crypto.model.js +23 -23
  36. package/src/api/crypto/crypto.router.js +20 -20
  37. package/src/api/crypto/crypto.service.js +64 -64
  38. package/src/api/default/default.controller.js +69 -69
  39. package/src/api/default/default.model.js +20 -20
  40. package/src/api/default/default.router.js +23 -23
  41. package/src/api/default/default.service.js +31 -31
  42. package/src/api/file/file.controller.js +53 -51
  43. package/src/api/file/file.model.js +19 -19
  44. package/src/api/file/file.router.js +21 -20
  45. package/src/api/file/file.service.js +76 -70
  46. package/src/api/instance/instance.controller.js +69 -69
  47. package/src/api/instance/instance.model.js +36 -36
  48. package/src/api/instance/instance.router.js +33 -33
  49. package/src/api/instance/instance.service.js +48 -48
  50. package/src/api/test/test.controller.js +59 -59
  51. package/src/api/test/test.model.js +14 -14
  52. package/src/api/test/test.router.js +21 -21
  53. package/src/api/test/test.service.js +35 -35
  54. package/src/api/user/user.build.js +16 -0
  55. package/src/api/user/user.controller.js +70 -70
  56. package/src/api/user/user.model.js +65 -65
  57. package/src/api/user/user.router.js +345 -345
  58. package/src/api/user/user.service.js +479 -479
  59. package/src/api.js +23 -23
  60. package/src/client/Default.index.js +40 -40
  61. package/src/client/components/core/Account.js +290 -290
  62. package/src/client/components/core/AgGrid.js +160 -160
  63. package/src/client/components/core/Auth.js +19 -19
  64. package/src/client/components/core/Badge.js +32 -32
  65. package/src/client/components/core/{BlockChain.js → Blockchain.js} +41 -41
  66. package/src/client/components/core/Blog.js +9 -9
  67. package/src/client/components/core/BtnIcon.js +101 -94
  68. package/src/client/components/core/CalendarCore.js +458 -319
  69. package/src/client/components/core/Chat.js +64 -64
  70. package/src/client/components/core/ColorPalette.js +5267 -5267
  71. package/src/client/components/core/CommonJs.js +735 -732
  72. package/src/client/components/core/Content.js +193 -49
  73. package/src/client/components/core/Css.js +1064 -1027
  74. package/src/client/components/core/CssCore.js +817 -796
  75. package/src/client/components/core/D3Chart.js +44 -44
  76. package/src/client/components/core/Docs.js +229 -229
  77. package/src/client/components/core/DropDown.js +164 -164
  78. package/src/client/components/core/EventsUI.js +46 -54
  79. package/src/client/components/core/FileExplorer.js +699 -624
  80. package/src/client/components/core/FullScreen.js +45 -45
  81. package/src/client/components/core/Input.js +346 -259
  82. package/src/client/components/core/JoyStick.js +77 -77
  83. package/src/client/components/core/Keyboard.js +73 -73
  84. package/src/client/components/core/LoadingAnimation.js +179 -157
  85. package/src/client/components/core/LogIn.js +187 -181
  86. package/src/client/components/core/LogOut.js +58 -52
  87. package/src/client/components/core/Logger.js +26 -26
  88. package/src/client/components/core/Modal.js +1612 -1596
  89. package/src/client/components/core/NotificationManager.js +84 -84
  90. package/src/client/components/core/Panel.js +613 -413
  91. package/src/client/components/core/PanelForm.js +468 -0
  92. package/src/client/components/core/Polyhedron.js +162 -162
  93. package/src/client/components/core/Recover.js +204 -204
  94. package/src/client/components/core/Responsive.js +53 -53
  95. package/src/client/components/core/RichText.js +51 -27
  96. package/src/client/components/core/Router.js +76 -77
  97. package/src/client/components/core/Scroll.js +34 -0
  98. package/src/client/components/core/SignUp.js +125 -125
  99. package/src/client/components/core/SocketIo.js +72 -72
  100. package/src/client/components/core/Stream.js +113 -113
  101. package/src/client/components/core/ToggleSwitch.js +87 -87
  102. package/src/client/components/core/ToolTip.js +26 -26
  103. package/src/client/components/core/Translate.js +437 -408
  104. package/src/client/components/core/Validator.js +100 -100
  105. package/src/client/components/core/VanillaJs.js +460 -457
  106. package/src/client/components/core/Wallet.js +106 -106
  107. package/src/client/components/core/Webhook.js +25 -25
  108. package/src/client/components/core/Worker.js +272 -272
  109. package/src/client/components/default/CommonDefault.js +29 -29
  110. package/src/client/components/default/CssDefault.js +13 -13
  111. package/src/client/components/default/ElementsDefault.js +38 -38
  112. package/src/client/components/default/LogInDefault.js +41 -41
  113. package/src/client/components/default/LogOutDefault.js +28 -28
  114. package/src/client/components/default/MenuDefault.js +389 -389
  115. package/src/client/components/default/RoutesDefault.js +48 -48
  116. package/src/client/components/default/SettingsDefault.js +16 -16
  117. package/src/client/components/default/SignUpDefault.js +9 -9
  118. package/src/client/components/default/SocketIoDefault.js +54 -54
  119. package/src/client/components/default/TranslateDefault.js +7 -7
  120. package/src/client/public/default/assets/mailer/api-user-check.png +0 -0
  121. package/src/client/public/default/assets/mailer/api-user-invalid-token.png +0 -0
  122. package/src/client/public/default/assets/mailer/api-user-recover.png +0 -0
  123. package/src/client/public/default/browserconfig.xml +11 -11
  124. package/src/client/public/default/manifest.webmanifest +68 -68
  125. package/src/client/public/default/plantuml/client-conf.svg +1 -0
  126. package/src/client/public/default/plantuml/client-schema.svg +1 -0
  127. package/src/client/public/default/plantuml/cron-conf.svg +1 -0
  128. package/src/client/public/default/plantuml/cron-schema.svg +1 -0
  129. package/src/client/public/default/plantuml/server-conf.svg +1 -0
  130. package/src/client/public/default/plantuml/server-schema.svg +1 -0
  131. package/src/client/public/default/plantuml/ssr-conf.svg +1 -0
  132. package/src/client/public/default/plantuml/ssr-schema.svg +1 -0
  133. package/src/client/public/default/sitemap +147 -147
  134. package/src/client/public/default/yandex-browser-manifest.json +8 -8
  135. package/src/client/public/doc/sitemap +147 -147
  136. package/src/client/public/test/sitemap +147 -147
  137. package/src/client/services/core/core.service.js +170 -152
  138. package/src/client/services/crypto/crypto.service.js +70 -70
  139. package/src/client/services/default/default.management.js +345 -345
  140. package/src/client/services/default/default.service.js +89 -89
  141. package/src/client/services/file/file.service.js +70 -70
  142. package/src/client/services/instance/instance.management.js +74 -74
  143. package/src/client/services/instance/instance.service.js +89 -89
  144. package/src/client/services/test/test.service.js +70 -70
  145. package/src/client/services/user/user.management.js +50 -50
  146. package/src/client/services/user/user.service.js +89 -89
  147. package/src/client/ssr/Render.js +16 -16
  148. package/src/client/ssr/body-components/CacheControl.js +114 -113
  149. package/src/client/ssr/body-components/DefaultSplashScreen.js +79 -79
  150. package/src/client/ssr/email-components/DefaultRecoverEmail.js +21 -21
  151. package/src/client/ssr/email-components/DefaultVerifyEmail.js +17 -17
  152. package/src/client/ssr/head-components/Css.js +241 -241
  153. package/src/client/ssr/head-components/DefaultScripts.js +3 -3
  154. package/src/client/ssr/head-components/Microdata.js +11 -11
  155. package/src/client/ssr/head-components/Production.js +1 -1
  156. package/src/client/ssr/head-components/PwaDefault.js +59 -59
  157. package/src/client/ssr/head-components/Seo.js +14 -14
  158. package/src/client/sw/default.sw.js +201 -201
  159. package/src/client/sw/template.sw.js +84 -84
  160. package/src/client.build.js +22 -22
  161. package/src/client.dev.js +21 -21
  162. package/src/cron.js +25 -25
  163. package/src/db/DataBaseProvider.js +34 -34
  164. package/src/db/mariadb/MariaDB.js +33 -33
  165. package/src/db/mongo/MongooseDB.js +137 -46
  166. package/src/dns.js +22 -22
  167. package/src/index.js +42 -29
  168. package/src/mailer/EmailRender.js +69 -69
  169. package/src/mailer/MailerProvider.js +96 -96
  170. package/src/proxy.js +22 -22
  171. package/src/runtime/lampp/Lampp.js +115 -44
  172. package/src/runtime/nginx/Nginx.js +3 -3
  173. package/src/runtime/xampp/Xampp.js +49 -49
  174. package/src/server/auth.js +235 -204
  175. package/src/server/backup.js +101 -94
  176. package/src/server/client-build-live.js +72 -72
  177. package/src/server/client-build.js +699 -699
  178. package/src/server/client-dev-server.js +60 -58
  179. package/src/server/client-formatted.js +48 -48
  180. package/src/server/client-icons.js +149 -150
  181. package/src/server/conf.js +860 -611
  182. package/src/server/dns.js +98 -98
  183. package/src/server/downloader.js +42 -42
  184. package/src/server/logger.js +190 -180
  185. package/src/server/network.js +122 -122
  186. package/src/server/peer.js +33 -33
  187. package/src/server/process.js +66 -66
  188. package/src/server/prompt-optimizer.js +28 -28
  189. package/src/server/proxy.js +118 -118
  190. package/src/server/runtime.js +444 -393
  191. package/src/server/ssl.js +120 -107
  192. package/src/server.js +25 -25
  193. package/src/ws/IoInterface.js +45 -45
  194. package/src/ws/IoServer.js +39 -39
  195. package/src/ws/core/channels/core.ws.chat.js +23 -23
  196. package/src/ws/core/channels/core.ws.mailer.js +35 -35
  197. package/src/ws/core/channels/core.ws.stream.js +31 -31
  198. package/src/ws/core/core.ws.connection.js +28 -28
  199. package/src/ws/core/core.ws.emit.js +14 -14
  200. package/src/ws/core/core.ws.server.js +24 -24
  201. package/src/ws/core/management/core.ws.chat.js +8 -8
  202. package/src/ws/core/management/core.ws.mailer.js +16 -16
  203. package/src/ws/core/management/core.ws.stream.js +8 -8
  204. package/src/ws/default/channels/default.ws.main.js +16 -16
  205. package/src/ws/default/default.ws.connection.js +22 -22
  206. package/src/ws/default/default.ws.emit.js +14 -14
  207. package/src/ws/default/default.ws.server.js +20 -20
  208. package/src/ws/default/management/default.ws.main.js +8 -8
  209. package/startup.js +11 -11
  210. package/supervisord-openssh-server.conf +4 -4
  211. package/test/api.test.js +60 -60
  212. package/bin/dns.js +0 -1
  213. package/bin/install.js +0 -357
  214. package/bin/shortcut.js +0 -44
@@ -1,699 +1,699 @@
1
- 'use strict';
2
-
3
- import fs from 'fs-extra';
4
- import { srcFormatted, componentFormatted, viewFormatted } from './client-formatted.js';
5
- import { loggerFactory } from './logger.js';
6
- import { cap, newInstance, orderArrayFromAttrInt, titleFormatted } from '../client/components/core/CommonJs.js';
7
- import UglifyJS from 'uglify-js';
8
- import { minify } from 'html-minifier-terser';
9
- import dotenv from 'dotenv';
10
- import AdmZip from 'adm-zip';
11
- import * as dir from 'path';
12
- import { shellExec } from './process.js';
13
- import swaggerAutoGen from 'swagger-autogen';
14
- import { SitemapStream, streamToPromise } from 'sitemap';
15
- import { Readable } from 'stream';
16
- import { buildIcons, buildTextImg, getBufferPngText } from './client-icons.js';
17
-
18
- dotenv.config();
19
-
20
- // Static Site Generation (SSG)
21
-
22
- const buildAcmeChallengePath = (acmeChallengeFullPath = '') => {
23
- fs.mkdirSync(acmeChallengeFullPath, {
24
- recursive: true,
25
- });
26
- fs.writeFileSync(`${acmeChallengeFullPath}/.gitkeep`, '', 'utf8');
27
- };
28
-
29
- const fullBuild = async ({
30
- path,
31
- logger,
32
- client,
33
- db,
34
- dists,
35
- rootClientPath,
36
- acmeChallengeFullPath,
37
- publicClientId,
38
- iconsBuild,
39
- metadata,
40
- }) => {
41
- logger.warn('Full build', rootClientPath);
42
-
43
- fs.removeSync(rootClientPath);
44
-
45
- buildAcmeChallengePath(acmeChallengeFullPath);
46
-
47
- if (fs.existsSync(`./src/client/public/${publicClientId}`)) {
48
- if (iconsBuild) {
49
- const defaultBaseIconFolderPath = `src/client/public/${publicClientId}/assets/logo`;
50
- if (!fs.existsSync(defaultBaseIconFolderPath)) fs.mkdirSync(defaultBaseIconFolderPath, { recursive: true });
51
- const defaultBaseIconPath = `${defaultBaseIconFolderPath}/base-icon.png`;
52
- if (!fs.existsSync(defaultBaseIconPath))
53
- await buildTextImg(metadata.title, { debugFilename: defaultBaseIconPath });
54
-
55
- if (path === '/' && !fs.existsSync(`./src/client/public/${publicClientId}/site.webmanifest`))
56
- await buildIcons({ publicClientId, metadata });
57
- }
58
- fs.copySync(
59
- `./src/client/public/${publicClientId}`,
60
- rootClientPath /* {
61
- filter: function (name) {
62
- console.log(name);
63
- return true;
64
- },
65
- } */,
66
- );
67
- } else if (fs.existsSync(`./engine-private/src/client/public/${publicClientId}`)) {
68
- switch (publicClientId) {
69
- case 'mysql_test':
70
- if (db) {
71
- fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath);
72
- fs.writeFileSync(
73
- `${rootClientPath}/index.php`,
74
- fs
75
- .readFileSync(`${rootClientPath}/index.php`, 'utf8')
76
- .replace('test_servername', 'localhost')
77
- .replace('test_username', db.user)
78
- .replace('test_password', db.password)
79
- .replace('test_dbname', db.name),
80
- 'utf8',
81
- );
82
- }
83
- break;
84
-
85
- default:
86
- break;
87
- }
88
- }
89
- if (dists)
90
- for (const dist of dists) {
91
- if ('folder' in dist) {
92
- fs.mkdirSync(`${rootClientPath}${dist.public_folder}`, { recursive: true });
93
- fs.copySync(dist.folder, `${rootClientPath}${dist.public_folder}`);
94
- }
95
- if ('styles' in dist) {
96
- fs.mkdirSync(`${rootClientPath}${dist.public_styles_folder}`, { recursive: true });
97
- fs.copySync(dist.styles, `${rootClientPath}${dist.public_styles_folder}`);
98
- }
99
- }
100
- };
101
-
102
- const buildClient = async (options = { liveClientBuildPaths: [], instances: [] }) => {
103
- const logger = loggerFactory(import.meta);
104
- const confClient = JSON.parse(fs.readFileSync(`./conf/conf.client.json`, 'utf8'));
105
- const confServer = JSON.parse(fs.readFileSync(`./conf/conf.server.json`, 'utf8'));
106
- const confSSR = JSON.parse(fs.readFileSync(`./conf/conf.ssr.json`, 'utf8'));
107
- const packageData = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
108
- const acmeChallengePath = `/.well-known/acme-challenge`;
109
- const publicPath = `./public`;
110
-
111
- // { srcBuildPath, publicBuildPath }
112
- const enableLiveRebuild =
113
- options && options.liveClientBuildPaths && options.liveClientBuildPaths.length > 0 ? true : false;
114
-
115
- let currentPort = parseInt(process.env.PORT) + 1;
116
- for (const host of Object.keys(confServer)) {
117
- const paths = orderArrayFromAttrInt(Object.keys(confServer[host]), 'length', 'asc');
118
- for (const path of paths) {
119
- if (
120
- options &&
121
- options.instances &&
122
- options.instances.length > 0 &&
123
- !options.instances.find((i) => i.path === path && i.host === host)
124
- )
125
- continue;
126
- const {
127
- runtime,
128
- client,
129
- directory,
130
- disabledRebuild,
131
- minifyBuild,
132
- db,
133
- redirect,
134
- apis,
135
- iconsBuild,
136
- docsBuild,
137
- apiBaseProxyPath,
138
- apiBaseHost,
139
- ttiLoadTimeLimit,
140
- singleReplica,
141
- } = confServer[host][path];
142
- if (singleReplica) continue;
143
- if (!confClient[client]) confClient[client] = {};
144
- const { components, dists, views, services, metadata, publicRef } = confClient[client];
145
- let backgroundImage;
146
- if (metadata) {
147
- backgroundImage = metadata.backgroundImage;
148
- if (metadata.thumbnail) metadata.thumbnail = `${path === '/' ? path : `${path}/`}${metadata.thumbnail}`;
149
- }
150
- const rootClientPath = directory ? directory : `${publicPath}/${host}${path}`;
151
- const port = newInstance(currentPort);
152
- const publicClientId = publicRef ? publicRef : client;
153
- const fullBuildEnabled = !process.argv.includes('l') && !confServer[host][path].liteBuild && !enableLiveRebuild;
154
- // const baseHost = process.env.NODE_ENV === 'production' ? `https://${host}` : `http://localhost:${port}`;
155
- const baseHost = process.env.NODE_ENV === 'production' ? `https://${host}` : ``;
156
- // ''; // process.env.NODE_ENV === 'production' ? `https://${host}` : ``;
157
- currentPort++;
158
-
159
- const acmeChallengeFullPath = directory
160
- ? `${directory}${acmeChallengePath}`
161
- : `${publicPath}/${host}${acmeChallengePath}`;
162
-
163
- if (!enableLiveRebuild) buildAcmeChallengePath(acmeChallengeFullPath);
164
-
165
- if (redirect || disabledRebuild) continue;
166
-
167
- if (!enableLiveRebuild && runtime === 'lampp' && client === 'wordpress') {
168
- shellExec(`node bin/install linux wordpress ${host}${path}`);
169
- shellExec(`node bin/db ${host}${path} create`);
170
- continue;
171
- }
172
-
173
- if (fullBuildEnabled)
174
- // !(confServer[host]['/'] && confServer[host]['/'].liteBuild)
175
- await fullBuild({
176
- path,
177
- logger,
178
- client,
179
- db,
180
- dists,
181
- rootClientPath,
182
- acmeChallengeFullPath,
183
- publicClientId,
184
- iconsBuild,
185
- metadata,
186
- });
187
-
188
- if (components)
189
- for (const module of Object.keys(components)) {
190
- if (!fs.existsSync(`${rootClientPath}/components/${module}`))
191
- fs.mkdirSync(`${rootClientPath}/components/${module}`, { recursive: true });
192
-
193
- for (const component of components[module]) {
194
- const jsSrcPath = `./src/client/components/${module}/${component}.js`;
195
- const jsPublicPath = `${rootClientPath}/components/${module}/${component}.js`;
196
-
197
- if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
198
-
199
- const jsSrc = componentFormatted(
200
- await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')),
201
- module,
202
- dists,
203
- path,
204
- 'components',
205
- baseHost,
206
- );
207
- fs.writeFileSync(
208
- jsPublicPath,
209
- minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
210
- 'utf8',
211
- );
212
- }
213
- }
214
-
215
- if (services) {
216
- for (const module of services) {
217
- if (!fs.existsSync(`${rootClientPath}/services/${module}`))
218
- fs.mkdirSync(`${rootClientPath}/services/${module}`, { recursive: true });
219
-
220
- if (fs.existsSync(`./src/client/services/${module}/${module}.service.js`)) {
221
- const jsSrcPath = `./src/client/services/${module}/${module}.service.js`;
222
- const jsPublicPath = `${rootClientPath}/services/${module}/${module}.service.js`;
223
- if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
224
-
225
- let jsSrc = componentFormatted(
226
- await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')),
227
- module,
228
- dists,
229
- path,
230
- 'services',
231
- baseHost,
232
- );
233
- if (module === 'core' && process.env.NODE_ENV === 'production') {
234
- if (apiBaseHost)
235
- jsSrc = jsSrc.replace(
236
- 'const getBaseHost = () => location.host;',
237
- `const getBaseHost = () => '${apiBaseHost}';`,
238
- );
239
- if (apiBaseProxyPath)
240
- jsSrc = jsSrc.replace('${getProxyPath()}api/', `${apiBaseProxyPath}${process.env.BASE_API}/`);
241
- }
242
- fs.writeFileSync(
243
- jsPublicPath,
244
- minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
245
- 'utf8',
246
- );
247
- }
248
- }
249
-
250
- for (const module of services) {
251
- if (fs.existsSync(`./src/client/services/${module}/${module}.management.js`)) {
252
- const jsSrcPath = `./src/client/services/${module}/${module}.management.js`;
253
- const jsPublicPath = `${rootClientPath}/services/${module}/${module}.management.js`;
254
- if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
255
-
256
- const jsSrc = componentFormatted(
257
- await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')),
258
- module,
259
- dists,
260
- path,
261
- 'services',
262
- baseHost,
263
- );
264
- fs.writeFileSync(
265
- jsPublicPath,
266
- minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
267
- 'utf8',
268
- );
269
- }
270
- }
271
- }
272
-
273
- const buildId = `${client}.index`;
274
- const siteMapLinks = [];
275
-
276
- if (views) {
277
- // build service worker
278
- if (path === '/') {
279
- const jsSrcPath = fs.existsSync(`./src/client/sw/${publicClientId}.sw.js`)
280
- ? `./src/client/sw/${publicClientId}.sw.js`
281
- : `./src/client/sw/default.sw.js`;
282
-
283
- const jsPublicPath = `${rootClientPath}/sw.js`;
284
-
285
- if (!(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath))) {
286
- const jsSrc = viewFormatted(await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')), dists, path, baseHost);
287
-
288
- fs.writeFileSync(
289
- jsPublicPath,
290
- minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
291
- 'utf8',
292
- );
293
- }
294
- }
295
- if (
296
- !(
297
- enableLiveRebuild &&
298
- !options.liveClientBuildPaths.find(
299
- (p) => p.srcBuildPath.startsWith(`./src/client/ssr`) || p.srcBuildPath.slice(-9) === '.index.js',
300
- )
301
- )
302
- )
303
- for (const view of views) {
304
- const buildPath = `${
305
- rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
306
- }${view.path === '/' ? view.path : `${view.path}/`}`;
307
-
308
- if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
309
-
310
- logger.info('View build', buildPath);
311
-
312
- const jsSrc = viewFormatted(
313
- await srcFormatted(fs.readFileSync(`./src/client/${view.client}.index.js`, 'utf8')),
314
- dists,
315
- path,
316
- baseHost,
317
- );
318
-
319
- fs.writeFileSync(
320
- `${buildPath}${buildId}.js`,
321
- minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
322
- 'utf8',
323
- );
324
-
325
- // const title = `${metadata && metadata.title ? metadata.title : cap(client)}${
326
- // view.title ? ` | ${view.title}` : view.path !== '/' ? ` | ${titleFormatted(view.path)}` : ''
327
- // }`;
328
-
329
- const title = `${
330
- view.title ? `${view.title} | ` : view.path !== '/' ? `${titleFormatted(view.path)} | ` : ''
331
- }${metadata && metadata.title ? metadata.title : cap(client)}`;
332
-
333
- const canonicalURL = `https://${host}${path}${
334
- view.path === '/' ? (path === '/' ? '' : '/') : path === '/' ? `${view.path.slice(1)}/` : `${view.path}/`
335
- }`;
336
- const ssrPath = path === '/' ? path : `${path}/`;
337
-
338
- let ssrHeadComponents = ``;
339
- let ssrBodyComponents = ``;
340
- if ('ssr' in view) {
341
- // https://metatags.io/
342
- if (process.env.NODE_ENV === 'production' && !confSSR[view.ssr].head.includes('Production'))
343
- confSSR[view.ssr].head.unshift('Production');
344
-
345
- for (const ssrHeadComponent of confSSR[view.ssr].head) {
346
- let SrrComponent;
347
- eval(
348
- await srcFormatted(
349
- fs.readFileSync(`./src/client/ssr/head-components/${ssrHeadComponent}.js`, 'utf8'),
350
- ),
351
- );
352
-
353
- switch (ssrHeadComponent) {
354
- case 'Pwa':
355
- const validPwaBuild =
356
- metadata &&
357
- fs.existsSync(`./src/client/public/${publicClientId}/browserconfig.xml`) &&
358
- fs.existsSync(`./src/client/public/${publicClientId}/site.webmanifest`);
359
-
360
- if (view.path === '/' && validPwaBuild) {
361
- // build webmanifest
362
- const webmanifestJson = JSON.parse(
363
- fs.readFileSync(`./src/client/public/${publicClientId}/site.webmanifest`, 'utf8'),
364
- );
365
- if (metadata.title) {
366
- webmanifestJson.name = metadata.title;
367
- webmanifestJson.short_name = metadata.title;
368
- }
369
- if (metadata.description) {
370
- webmanifestJson.description = metadata.description;
371
- }
372
- if (metadata.themeColor) {
373
- webmanifestJson.theme_color = metadata.themeColor;
374
- webmanifestJson.background_color = metadata.themeColor;
375
- }
376
- fs.writeFileSync(
377
- `${buildPath}site.webmanifest`,
378
- JSON.stringify(webmanifestJson, null, 4).replaceAll(`: "/`, `: "${ssrPath}`),
379
- 'utf8',
380
- );
381
- // build browserconfig
382
- fs.writeFileSync(
383
- `${buildPath}browserconfig.xml`,
384
- fs
385
- .readFileSync(`./src/client/public/${publicClientId}/browserconfig.xml`, 'utf8')
386
- .replaceAll(
387
- `<TileColor></TileColor>`,
388
- metadata.themeColor
389
- ? `<TileColor>${metadata.themeColor}</TileColor>`
390
- : `<TileColor>#e0e0e0</TileColor>`,
391
- )
392
- .replaceAll(`src="/`, `src="${ssrPath}`),
393
- 'utf8',
394
- );
395
-
396
- // Android play store example:
397
- //
398
- // "related_applications": [
399
- // {
400
- // "platform": "play",
401
- // "url": "https://play.google.com/store/apps/details?id=cheeaun.hackerweb"
402
- // }
403
- // ],
404
- // "prefer_related_applications": true
405
- }
406
- if (validPwaBuild) ssrHeadComponents += SrrComponent({ title, ssrPath, canonicalURL, ...metadata });
407
- break;
408
- case 'Seo':
409
- if (metadata) {
410
- ssrHeadComponents += SrrComponent({ title, ssrPath, canonicalURL, ...metadata });
411
- }
412
- break;
413
- case 'Microdata':
414
- if (
415
- fs.existsSync(`./src/client/public/${publicClientId}/microdata.json`) // &&
416
- // path === '/' &&
417
- // view.path === '/'
418
- ) {
419
- const microdata = JSON.parse(
420
- fs.readFileSync(`./src/client/public/${publicClientId}/microdata.json`, 'utf8'),
421
- );
422
- ssrHeadComponents += SrrComponent({ microdata });
423
- }
424
- break;
425
- default:
426
- ssrHeadComponents += SrrComponent({ ssrPath, host, path });
427
- break;
428
- }
429
- }
430
-
431
- for (const ssrBodyComponent of confSSR[view.ssr].body) {
432
- let SrrComponent;
433
- eval(
434
- await srcFormatted(
435
- fs.readFileSync(`./src/client/ssr/body-components/${ssrBodyComponent}.js`, 'utf8'),
436
- ),
437
- );
438
- switch (ssrBodyComponent) {
439
- case 'UnderpostDefaultSplashScreen':
440
- case 'CyberiaDefaultSplashScreen':
441
- case 'NexodevSplashScreen':
442
- case 'DefaultSplashScreen':
443
- if (backgroundImage) {
444
- ssrHeadComponents += SrrComponent({
445
- base64BackgroundImage: `data:image/${backgroundImage.split('.').pop()};base64,${fs
446
- .readFileSync(backgroundImage)
447
- .toString('base64')}`,
448
- });
449
- } else {
450
- const bufferBackgroundImage = await getBufferPngText({
451
- text: ' ',
452
- textColor: metadata?.themeColor ? metadata.themeColor : '#ececec',
453
- size: '100x100',
454
- bgColor: metadata?.themeColor ? metadata.themeColor : '#ececec',
455
- });
456
- ssrHeadComponents += SrrComponent({
457
- base64BackgroundImage: `data:image/png;base64,${bufferBackgroundImage.toString('base64')}`,
458
- });
459
- }
460
- break;
461
-
462
- default:
463
- ssrBodyComponents += SrrComponent({ ssrPath, host, path, ttiLoadTimeLimit });
464
- break;
465
- }
466
- }
467
- }
468
-
469
- let Render = () => '';
470
- eval(await srcFormatted(fs.readFileSync(`./src/client/ssr/Render.js`, 'utf8')));
471
-
472
- const htmlSrc = Render({
473
- title,
474
- buildId,
475
- ssrPath,
476
- ssrHeadComponents,
477
- ssrBodyComponents,
478
- });
479
-
480
- /** @type {import('sitemap').SitemapItem} */
481
- const siteMapLink = {
482
- url: `${path === '/' ? '' : path}${view.path}`,
483
- changefreq: 'daily',
484
- priority: 0.8,
485
- };
486
- siteMapLinks.push(siteMapLink);
487
-
488
- fs.writeFileSync(
489
- `${buildPath}index.html`,
490
- minifyBuild || process.env.NODE_ENV === 'production'
491
- ? await minify(htmlSrc, {
492
- minifyCSS: true,
493
- minifyJS: true,
494
- collapseBooleanAttributes: true,
495
- collapseInlineTagWhitespace: true,
496
- collapseWhitespace: true,
497
- })
498
- : htmlSrc,
499
- 'utf8',
500
- );
501
- }
502
- }
503
- if (!enableLiveRebuild && siteMapLinks.length > 0) {
504
- const xslUrl = fs.existsSync(`${rootClientPath}/sitemap`)
505
- ? `${path === '/' ? '' : path}/sitemap.xsl`
506
- : undefined;
507
- // Create a stream to write to
508
- /** @type {import('sitemap').SitemapStreamOptions} */
509
- const sitemapOptions = { hostname: `https://${host}`, xslUrl };
510
-
511
- const siteMapStream = new SitemapStream(sitemapOptions);
512
- let siteMapSrc = await new Promise((resolve) =>
513
- streamToPromise(Readable.from(siteMapLinks).pipe(siteMapStream)).then((data) => resolve(data.toString())),
514
- );
515
- switch (publicClientId) {
516
- case 'underpost':
517
- siteMapSrc = siteMapSrc.replaceAll(
518
- `</urlset>`,
519
- `${fs.readFileSync(`./src/client/public/underpost/sitemap-template.txt`, 'utf8')} </urlset>`,
520
- );
521
- break;
522
-
523
- default:
524
- break;
525
- }
526
- // Return a promise that resolves with your XML string
527
- fs.writeFileSync(`${rootClientPath}/sitemap.xml`, siteMapSrc, 'utf8');
528
- if (xslUrl)
529
- fs.writeFileSync(
530
- `${rootClientPath}/sitemap.xsl`,
531
- fs.readFileSync(`${rootClientPath}/sitemap`, 'utf8').replaceAll('{{web-url}}', `https://${host}${path}`),
532
- 'utf8',
533
- );
534
-
535
- fs.writeFileSync(
536
- `${rootClientPath}/robots.txt`,
537
- `User-agent: *
538
- Sitemap: https://${host}${path === '/' ? '' : path}/sitemap.xml`,
539
- 'utf8',
540
- );
541
- }
542
-
543
- if (!enableLiveRebuild && !process.argv.includes('l') && !process.argv.includes('deploy') && docsBuild) {
544
- // fullBuildEnabled || process.argv.includes('docs')
545
-
546
- // https://www.pullrequest.com/blog/leveraging-jsdoc-for-better-code-documentation-in-javascript/
547
- // https://jsdoc.app/about-configuring-jsdoc
548
- // https://jsdoc.app/ Block tags
549
-
550
- // "theme_opts": {
551
- // "default_theme": "dark" // "light", "fallback-dark", "fallback-light"
552
- // }
553
-
554
- const jsDocsConfig = JSON.parse(fs.readFileSync(`./jsdoc.json`, 'utf8'));
555
- jsDocsConfig.opts.destination = `./public/${host}${path === '/' ? path : `${path}/`}docs/`;
556
- jsDocsConfig.opts.theme_opts.title = metadata && metadata.title ? metadata.title : undefined;
557
- jsDocsConfig.opts.theme_opts.favicon = `./public/${host}${path === '/' ? path : `${path}/favicon.ico`}`;
558
- fs.writeFileSync(`./jsdoc.json`, JSON.stringify(jsDocsConfig, null, 4), 'utf8');
559
- logger.warn('build jsdoc view', jsDocsConfig.opts.destination);
560
- shellExec(`npm run docs`, { silent: true });
561
-
562
- // coverage
563
- if (!fs.existsSync(`./coverage`)) {
564
- shellExec(`npm test`);
565
- }
566
- const coverageBuildPath = `${jsDocsConfig.opts.destination}/coverage`;
567
- fs.mkdirSync(coverageBuildPath, { recursive: true });
568
- fs.copySync(`./coverage`, coverageBuildPath);
569
-
570
- // uml
571
- shellExec(`node bin/deploy uml ${host} ${path}`);
572
-
573
- // https://swagger-autogen.github.io/docs/
574
-
575
- const basePath = path === '/' ? `${process.env.BASE_API}` : `/${process.env.BASE_API}`;
576
-
577
- const doc = {
578
- info: {
579
- version: packageData.version, // by default: '1.0.0'
580
- title: metadata?.title ? `${metadata.title}` : 'REST API', // by default: 'REST API'
581
- description: metadata?.description ? metadata.description : '', // by default: ''
582
- },
583
- servers: [
584
- {
585
- url:
586
- process.env.NODE_ENV === 'development'
587
- ? `http://localhost:${port}${path}${basePath}`
588
- : `https://${host}${path}${basePath}`, // by default: 'http://localhost:3000'
589
- description: `${process.env.NODE_ENV} server`, // by default: ''
590
- },
591
- ],
592
- tags: [
593
- // by default: empty Array
594
- {
595
- name: 'user', // Tag name
596
- description: 'User API operations', // Tag description
597
- },
598
- ],
599
- components: {
600
- schemas: {
601
- userRequest: {
602
- username: 'user123',
603
- password: 'Password123',
604
- email: 'user@example.com',
605
- },
606
- userResponse: {
607
- status: 'success',
608
- data: {
609
- token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjY2YzM3N2Y1N2Y5OWU1OTY5YjgxZG...',
610
- user: {
611
- _id: '66c377f57f99e5969b81de89',
612
- email: 'user@example.com',
613
- emailConfirmed: false,
614
- username: 'user123',
615
- role: 'user',
616
- profileImageId: '66c377f57f99e5969b81de87',
617
- },
618
- },
619
- },
620
- userUpdateResponse: {
621
- status: 'success',
622
- data: {
623
- _id: '66c377f57f99e5969b81de89',
624
- email: 'user@example.com',
625
- emailConfirmed: false,
626
- username: 'user123222',
627
- role: 'user',
628
- profileImageId: '66c377f57f99e5969b81de87',
629
- },
630
- },
631
- userGetResponse: {
632
- status: 'success',
633
- data: {
634
- _id: '66c377f57f99e5969b81de89',
635
- email: 'user@example.com',
636
- emailConfirmed: false,
637
- username: 'user123222',
638
- role: 'user',
639
- profileImageId: '66c377f57f99e5969b81de87',
640
- },
641
- },
642
- userLogInRequest: {
643
- email: 'user@example.com',
644
- password: 'Password123',
645
- },
646
- userBadRequestResponse: {
647
- status: 'error',
648
- message: 'Bad request. Please check your inputs, and try again',
649
- },
650
- },
651
- securitySchemes: {
652
- bearerAuth: {
653
- type: 'http',
654
- scheme: 'bearer',
655
- },
656
- },
657
- },
658
- };
659
-
660
- logger.warn('build swagger api docs', doc.info);
661
-
662
- const outputFile = `./public/${host}${path === '/' ? path : `${path}/`}swagger-output.json`;
663
- const routes = [];
664
- for (const api of apis) {
665
- if (['user'].includes(api)) routes.push(`./src/api/${api}/${api}.router.js`);
666
- }
667
-
668
- /* NOTE: If you are using the express Router, you must pass in the 'routes' only the
669
- root file where the route starts, such as index.js, app.js, routes.js, etc ... */
670
-
671
- await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
672
- }
673
- if (!enableLiveRebuild && process.argv[2] === 'build-full-client-zip') {
674
- logger.warn('build zip', rootClientPath);
675
-
676
- if (!fs.existsSync('./build')) fs.mkdirSync('./build');
677
-
678
- const zip = new AdmZip();
679
- const files = await fs.readdir(rootClientPath, { recursive: true });
680
-
681
- for (const relativePath of files) {
682
- const filePath = dir.resolve(`${rootClientPath}/${relativePath}`);
683
- if (!fs.lstatSync(filePath).isDirectory()) {
684
- const folder = dir.relative(`public/${host}${path}`, dir.dirname(filePath));
685
- zip.addLocalFile(filePath, folder);
686
- }
687
- }
688
-
689
- const buildId = `${host}-${path.replaceAll('/', '')}`;
690
-
691
- logger.warn('write zip', `./build/${buildId}.zip`);
692
-
693
- zip.writeZip(`./build/${buildId}.zip`);
694
- }
695
- }
696
- }
697
- };
698
-
699
- export { buildClient };
1
+ 'use strict';
2
+
3
+ import fs from 'fs-extra';
4
+ import { srcFormatted, componentFormatted, viewFormatted } from './client-formatted.js';
5
+ import { loggerFactory } from './logger.js';
6
+ import { cap, newInstance, orderArrayFromAttrInt, titleFormatted } from '../client/components/core/CommonJs.js';
7
+ import UglifyJS from 'uglify-js';
8
+ import { minify } from 'html-minifier-terser';
9
+ import dotenv from 'dotenv';
10
+ import AdmZip from 'adm-zip';
11
+ import * as dir from 'path';
12
+ import { shellExec } from './process.js';
13
+ import swaggerAutoGen from 'swagger-autogen';
14
+ import { SitemapStream, streamToPromise } from 'sitemap';
15
+ import { Readable } from 'stream';
16
+ import { buildIcons, buildTextImg, getBufferPngText } from './client-icons.js';
17
+
18
+ dotenv.config();
19
+
20
+ // Static Site Generation (SSG)
21
+
22
+ const buildAcmeChallengePath = (acmeChallengeFullPath = '') => {
23
+ fs.mkdirSync(acmeChallengeFullPath, {
24
+ recursive: true,
25
+ });
26
+ fs.writeFileSync(`${acmeChallengeFullPath}/.gitkeep`, '', 'utf8');
27
+ };
28
+
29
+ const fullBuild = async ({
30
+ path,
31
+ logger,
32
+ client,
33
+ db,
34
+ dists,
35
+ rootClientPath,
36
+ acmeChallengeFullPath,
37
+ publicClientId,
38
+ iconsBuild,
39
+ metadata,
40
+ }) => {
41
+ logger.warn('Full build', rootClientPath);
42
+
43
+ fs.removeSync(rootClientPath);
44
+
45
+ buildAcmeChallengePath(acmeChallengeFullPath);
46
+
47
+ if (fs.existsSync(`./src/client/public/${publicClientId}`)) {
48
+ if (iconsBuild) {
49
+ const defaultBaseIconFolderPath = `src/client/public/${publicClientId}/assets/logo`;
50
+ if (!fs.existsSync(defaultBaseIconFolderPath)) fs.mkdirSync(defaultBaseIconFolderPath, { recursive: true });
51
+ const defaultBaseIconPath = `${defaultBaseIconFolderPath}/base-icon.png`;
52
+ if (!fs.existsSync(defaultBaseIconPath))
53
+ await buildTextImg(metadata.title, { debugFilename: defaultBaseIconPath });
54
+
55
+ if (path === '/' && !fs.existsSync(`./src/client/public/${publicClientId}/site.webmanifest`))
56
+ await buildIcons({ publicClientId, metadata });
57
+ }
58
+ fs.copySync(
59
+ `./src/client/public/${publicClientId}`,
60
+ rootClientPath /* {
61
+ filter: function (name) {
62
+ console.log(name);
63
+ return true;
64
+ },
65
+ } */,
66
+ );
67
+ } else if (fs.existsSync(`./engine-private/src/client/public/${publicClientId}`)) {
68
+ switch (publicClientId) {
69
+ case 'mysql_test':
70
+ if (db) {
71
+ fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath);
72
+ fs.writeFileSync(
73
+ `${rootClientPath}/index.php`,
74
+ fs
75
+ .readFileSync(`${rootClientPath}/index.php`, 'utf8')
76
+ .replace('test_servername', 'localhost')
77
+ .replace('test_username', db.user)
78
+ .replace('test_password', db.password)
79
+ .replace('test_dbname', db.name),
80
+ 'utf8',
81
+ );
82
+ } else logger.error('not provided db config');
83
+ break;
84
+
85
+ default:
86
+ break;
87
+ }
88
+ }
89
+ if (dists)
90
+ for (const dist of dists) {
91
+ if ('folder' in dist) {
92
+ fs.mkdirSync(`${rootClientPath}${dist.public_folder}`, { recursive: true });
93
+ fs.copySync(dist.folder, `${rootClientPath}${dist.public_folder}`);
94
+ }
95
+ if ('styles' in dist) {
96
+ fs.mkdirSync(`${rootClientPath}${dist.public_styles_folder}`, { recursive: true });
97
+ fs.copySync(dist.styles, `${rootClientPath}${dist.public_styles_folder}`);
98
+ }
99
+ }
100
+ };
101
+
102
+ const buildClient = async (options = { liveClientBuildPaths: [], instances: [] }) => {
103
+ const logger = loggerFactory(import.meta);
104
+ const confClient = JSON.parse(fs.readFileSync(`./conf/conf.client.json`, 'utf8'));
105
+ const confServer = JSON.parse(fs.readFileSync(`./conf/conf.server.json`, 'utf8'));
106
+ const confSSR = JSON.parse(fs.readFileSync(`./conf/conf.ssr.json`, 'utf8'));
107
+ const packageData = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
108
+ const acmeChallengePath = `/.well-known/acme-challenge`;
109
+ const publicPath = `./public`;
110
+
111
+ // { srcBuildPath, publicBuildPath }
112
+ const enableLiveRebuild =
113
+ options && options.liveClientBuildPaths && options.liveClientBuildPaths.length > 0 ? true : false;
114
+
115
+ let currentPort = parseInt(process.env.PORT) + 1;
116
+ for (const host of Object.keys(confServer)) {
117
+ const paths = orderArrayFromAttrInt(Object.keys(confServer[host]), 'length', 'asc');
118
+ for (const path of paths) {
119
+ if (
120
+ options &&
121
+ options.instances &&
122
+ options.instances.length > 0 &&
123
+ !options.instances.find((i) => i.path === path && i.host === host)
124
+ )
125
+ continue;
126
+ const {
127
+ runtime,
128
+ client,
129
+ directory,
130
+ disabledRebuild,
131
+ minifyBuild,
132
+ db,
133
+ redirect,
134
+ apis,
135
+ iconsBuild,
136
+ docsBuild,
137
+ apiBaseProxyPath,
138
+ apiBaseHost,
139
+ ttiLoadTimeLimit,
140
+ singleReplica,
141
+ } = confServer[host][path];
142
+ if (singleReplica) continue;
143
+ if (!confClient[client]) confClient[client] = {};
144
+ const { components, dists, views, services, metadata, publicRef } = confClient[client];
145
+ let backgroundImage;
146
+ if (metadata) {
147
+ backgroundImage = metadata.backgroundImage;
148
+ if (metadata.thumbnail) metadata.thumbnail = `${path === '/' ? path : `${path}/`}${metadata.thumbnail}`;
149
+ }
150
+ const rootClientPath = directory ? directory : `${publicPath}/${host}${path}`;
151
+ const port = newInstance(currentPort);
152
+ const publicClientId = publicRef ? publicRef : client;
153
+ const fullBuildEnabled = !process.argv.includes('l') && !confServer[host][path].liteBuild && !enableLiveRebuild;
154
+ // const baseHost = process.env.NODE_ENV === 'production' ? `https://${host}` : `http://localhost:${port}`;
155
+ const baseHost = process.env.NODE_ENV === 'production' ? `https://${host}` : ``;
156
+ // ''; // process.env.NODE_ENV === 'production' ? `https://${host}` : ``;
157
+ currentPort++;
158
+
159
+ const acmeChallengeFullPath = directory
160
+ ? `${directory}${acmeChallengePath}`
161
+ : `${publicPath}/${host}${acmeChallengePath}`;
162
+
163
+ if (!enableLiveRebuild) buildAcmeChallengePath(acmeChallengeFullPath);
164
+
165
+ if (redirect || disabledRebuild) continue;
166
+
167
+ if (fullBuildEnabled) {
168
+ // !(confServer[host]['/'] && confServer[host]['/'].liteBuild)
169
+ await fullBuild({
170
+ path,
171
+ logger,
172
+ client,
173
+ db,
174
+ dists,
175
+ rootClientPath,
176
+ acmeChallengeFullPath,
177
+ publicClientId,
178
+ iconsBuild,
179
+ metadata,
180
+ });
181
+ if (apis)
182
+ for (const apiBuildScript of apis) {
183
+ const scriptPath = `src/api/${apiBuildScript}/${apiBuildScript}.build.js`;
184
+ if (fs.existsSync(`./${scriptPath}`)) {
185
+ shellExec(`node ${scriptPath}`);
186
+ }
187
+ }
188
+ }
189
+
190
+ if (components)
191
+ for (const module of Object.keys(components)) {
192
+ if (!fs.existsSync(`${rootClientPath}/components/${module}`))
193
+ fs.mkdirSync(`${rootClientPath}/components/${module}`, { recursive: true });
194
+
195
+ for (const component of components[module]) {
196
+ const jsSrcPath = `./src/client/components/${module}/${component}.js`;
197
+ const jsPublicPath = `${rootClientPath}/components/${module}/${component}.js`;
198
+
199
+ if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
200
+
201
+ const jsSrc = componentFormatted(
202
+ await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')),
203
+ module,
204
+ dists,
205
+ path,
206
+ 'components',
207
+ baseHost,
208
+ );
209
+ fs.writeFileSync(
210
+ jsPublicPath,
211
+ minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
212
+ 'utf8',
213
+ );
214
+ }
215
+ }
216
+
217
+ if (services) {
218
+ for (const module of services) {
219
+ if (!fs.existsSync(`${rootClientPath}/services/${module}`))
220
+ fs.mkdirSync(`${rootClientPath}/services/${module}`, { recursive: true });
221
+
222
+ if (fs.existsSync(`./src/client/services/${module}/${module}.service.js`)) {
223
+ const jsSrcPath = `./src/client/services/${module}/${module}.service.js`;
224
+ const jsPublicPath = `${rootClientPath}/services/${module}/${module}.service.js`;
225
+ if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
226
+
227
+ let jsSrc = componentFormatted(
228
+ await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')),
229
+ module,
230
+ dists,
231
+ path,
232
+ 'services',
233
+ baseHost,
234
+ );
235
+ if (module === 'core' && (process.env.NODE_ENV === 'production' || process.argv.includes('static'))) {
236
+ if (apiBaseHost)
237
+ jsSrc = jsSrc.replace(
238
+ 'const getBaseHost = () => location.host;',
239
+ `const getBaseHost = () => '${apiBaseHost}';`,
240
+ );
241
+ if (apiBaseProxyPath)
242
+ jsSrc = jsSrc.replace('${getProxyPath()}api/', `${apiBaseProxyPath}${process.env.BASE_API}/`);
243
+ }
244
+ fs.writeFileSync(
245
+ jsPublicPath,
246
+ minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
247
+ 'utf8',
248
+ );
249
+ }
250
+ }
251
+
252
+ for (const module of services) {
253
+ if (fs.existsSync(`./src/client/services/${module}/${module}.management.js`)) {
254
+ const jsSrcPath = `./src/client/services/${module}/${module}.management.js`;
255
+ const jsPublicPath = `${rootClientPath}/services/${module}/${module}.management.js`;
256
+ if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
257
+
258
+ const jsSrc = componentFormatted(
259
+ await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')),
260
+ module,
261
+ dists,
262
+ path,
263
+ 'services',
264
+ baseHost,
265
+ );
266
+ fs.writeFileSync(
267
+ jsPublicPath,
268
+ minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
269
+ 'utf8',
270
+ );
271
+ }
272
+ }
273
+ }
274
+
275
+ const buildId = `${client}.index`;
276
+ const siteMapLinks = [];
277
+
278
+ if (views) {
279
+ // build service worker
280
+ if (path === '/') {
281
+ const jsSrcPath = fs.existsSync(`./src/client/sw/${publicClientId}.sw.js`)
282
+ ? `./src/client/sw/${publicClientId}.sw.js`
283
+ : `./src/client/sw/default.sw.js`;
284
+
285
+ const jsPublicPath = `${rootClientPath}/sw.js`;
286
+
287
+ if (!(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath))) {
288
+ const jsSrc = viewFormatted(await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')), dists, path, baseHost);
289
+
290
+ fs.writeFileSync(
291
+ jsPublicPath,
292
+ minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
293
+ 'utf8',
294
+ );
295
+ }
296
+ }
297
+ if (
298
+ !(
299
+ enableLiveRebuild &&
300
+ !options.liveClientBuildPaths.find(
301
+ (p) => p.srcBuildPath.startsWith(`./src/client/ssr`) || p.srcBuildPath.slice(-9) === '.index.js',
302
+ )
303
+ )
304
+ )
305
+ for (const view of views) {
306
+ const buildPath = `${
307
+ rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
308
+ }${view.path === '/' ? view.path : `${view.path}/`}`;
309
+
310
+ if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
311
+
312
+ logger.info('View build', buildPath);
313
+
314
+ const jsSrc = viewFormatted(
315
+ await srcFormatted(fs.readFileSync(`./src/client/${view.client}.index.js`, 'utf8')),
316
+ dists,
317
+ path,
318
+ baseHost,
319
+ );
320
+
321
+ fs.writeFileSync(
322
+ `${buildPath}${buildId}.js`,
323
+ minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
324
+ 'utf8',
325
+ );
326
+
327
+ // const title = `${metadata && metadata.title ? metadata.title : cap(client)}${
328
+ // view.title ? ` | ${view.title}` : view.path !== '/' ? ` | ${titleFormatted(view.path)}` : ''
329
+ // }`;
330
+
331
+ const title = `${
332
+ view.title ? `${view.title} | ` : view.path !== '/' ? `${titleFormatted(view.path)} | ` : ''
333
+ }${metadata && metadata.title ? metadata.title : cap(client)}`;
334
+
335
+ const canonicalURL = `https://${host}${path}${
336
+ view.path === '/' ? (path === '/' ? '' : '/') : path === '/' ? `${view.path.slice(1)}/` : `${view.path}/`
337
+ }`;
338
+ const ssrPath = path === '/' ? path : `${path}/`;
339
+
340
+ let ssrHeadComponents = ``;
341
+ let ssrBodyComponents = ``;
342
+ if ('ssr' in view) {
343
+ // https://metatags.io/
344
+ if (process.env.NODE_ENV === 'production' && !confSSR[view.ssr].head.includes('Production'))
345
+ confSSR[view.ssr].head.unshift('Production');
346
+
347
+ for (const ssrHeadComponent of confSSR[view.ssr].head) {
348
+ let SrrComponent;
349
+ eval(
350
+ await srcFormatted(
351
+ fs.readFileSync(`./src/client/ssr/head-components/${ssrHeadComponent}.js`, 'utf8'),
352
+ ),
353
+ );
354
+
355
+ switch (ssrHeadComponent) {
356
+ case 'Pwa':
357
+ const validPwaBuild =
358
+ metadata &&
359
+ fs.existsSync(`./src/client/public/${publicClientId}/browserconfig.xml`) &&
360
+ fs.existsSync(`./src/client/public/${publicClientId}/site.webmanifest`);
361
+
362
+ if (view.path === '/' && validPwaBuild) {
363
+ // build webmanifest
364
+ const webmanifestJson = JSON.parse(
365
+ fs.readFileSync(`./src/client/public/${publicClientId}/site.webmanifest`, 'utf8'),
366
+ );
367
+ if (metadata.title) {
368
+ webmanifestJson.name = metadata.title;
369
+ webmanifestJson.short_name = metadata.title;
370
+ }
371
+ if (metadata.description) {
372
+ webmanifestJson.description = metadata.description;
373
+ }
374
+ if (metadata.themeColor) {
375
+ webmanifestJson.theme_color = metadata.themeColor;
376
+ webmanifestJson.background_color = metadata.themeColor;
377
+ }
378
+ fs.writeFileSync(
379
+ `${buildPath}site.webmanifest`,
380
+ JSON.stringify(webmanifestJson, null, 4).replaceAll(`: "/`, `: "${ssrPath}`),
381
+ 'utf8',
382
+ );
383
+ // build browserconfig
384
+ fs.writeFileSync(
385
+ `${buildPath}browserconfig.xml`,
386
+ fs
387
+ .readFileSync(`./src/client/public/${publicClientId}/browserconfig.xml`, 'utf8')
388
+ .replaceAll(
389
+ `<TileColor></TileColor>`,
390
+ metadata.themeColor
391
+ ? `<TileColor>${metadata.themeColor}</TileColor>`
392
+ : `<TileColor>#e0e0e0</TileColor>`,
393
+ )
394
+ .replaceAll(`src="/`, `src="${ssrPath}`),
395
+ 'utf8',
396
+ );
397
+
398
+ // Android play store example:
399
+ //
400
+ // "related_applications": [
401
+ // {
402
+ // "platform": "play",
403
+ // "url": "https://play.google.com/store/apps/details?id=cheeaun.hackerweb"
404
+ // }
405
+ // ],
406
+ // "prefer_related_applications": true
407
+ }
408
+ if (validPwaBuild) ssrHeadComponents += SrrComponent({ title, ssrPath, canonicalURL, ...metadata });
409
+ break;
410
+ case 'Seo':
411
+ if (metadata) {
412
+ ssrHeadComponents += SrrComponent({ title, ssrPath, canonicalURL, ...metadata });
413
+ }
414
+ break;
415
+ case 'Microdata':
416
+ if (
417
+ fs.existsSync(`./src/client/public/${publicClientId}/microdata.json`) // &&
418
+ // path === '/' &&
419
+ // view.path === '/'
420
+ ) {
421
+ const microdata = JSON.parse(
422
+ fs.readFileSync(`./src/client/public/${publicClientId}/microdata.json`, 'utf8'),
423
+ );
424
+ ssrHeadComponents += SrrComponent({ microdata });
425
+ }
426
+ break;
427
+ default:
428
+ ssrHeadComponents += SrrComponent({ ssrPath, host, path });
429
+ break;
430
+ }
431
+ }
432
+
433
+ for (const ssrBodyComponent of confSSR[view.ssr].body) {
434
+ let SrrComponent;
435
+ eval(
436
+ await srcFormatted(
437
+ fs.readFileSync(`./src/client/ssr/body-components/${ssrBodyComponent}.js`, 'utf8'),
438
+ ),
439
+ );
440
+ switch (ssrBodyComponent) {
441
+ case 'UnderpostDefaultSplashScreen':
442
+ case 'CyberiaDefaultSplashScreen':
443
+ case 'NexodevSplashScreen':
444
+ case 'DefaultSplashScreen':
445
+ if (backgroundImage) {
446
+ ssrHeadComponents += SrrComponent({
447
+ base64BackgroundImage: `data:image/${backgroundImage.split('.').pop()};base64,${fs
448
+ .readFileSync(backgroundImage)
449
+ .toString('base64')}`,
450
+ });
451
+ } else {
452
+ const bufferBackgroundImage = await getBufferPngText({
453
+ text: ' ',
454
+ textColor: metadata?.themeColor ? metadata.themeColor : '#ececec',
455
+ size: '100x100',
456
+ bgColor: metadata?.themeColor ? metadata.themeColor : '#ececec',
457
+ });
458
+ ssrHeadComponents += SrrComponent({
459
+ base64BackgroundImage: `data:image/png;base64,${bufferBackgroundImage.toString('base64')}`,
460
+ });
461
+ }
462
+ break;
463
+
464
+ default:
465
+ ssrBodyComponents += SrrComponent({ ssrPath, host, path, ttiLoadTimeLimit });
466
+ break;
467
+ }
468
+ }
469
+ }
470
+
471
+ let Render = () => '';
472
+ eval(await srcFormatted(fs.readFileSync(`./src/client/ssr/Render.js`, 'utf8')));
473
+
474
+ const htmlSrc = Render({
475
+ title,
476
+ buildId,
477
+ ssrPath,
478
+ ssrHeadComponents,
479
+ ssrBodyComponents,
480
+ });
481
+
482
+ /** @type {import('sitemap').SitemapItem} */
483
+ const siteMapLink = {
484
+ url: `${path === '/' ? '' : path}${view.path}`,
485
+ changefreq: 'daily',
486
+ priority: 0.8,
487
+ };
488
+ siteMapLinks.push(siteMapLink);
489
+
490
+ fs.writeFileSync(
491
+ `${buildPath}index.html`,
492
+ minifyBuild || process.env.NODE_ENV === 'production'
493
+ ? await minify(htmlSrc, {
494
+ minifyCSS: true,
495
+ minifyJS: true,
496
+ collapseBooleanAttributes: true,
497
+ collapseInlineTagWhitespace: true,
498
+ collapseWhitespace: true,
499
+ })
500
+ : htmlSrc,
501
+ 'utf8',
502
+ );
503
+ }
504
+ }
505
+ if (!enableLiveRebuild && siteMapLinks.length > 0) {
506
+ const xslUrl = fs.existsSync(`${rootClientPath}/sitemap`)
507
+ ? `${path === '/' ? '' : path}/sitemap.xsl`
508
+ : undefined;
509
+ // Create a stream to write to
510
+ /** @type {import('sitemap').SitemapStreamOptions} */
511
+ const sitemapOptions = { hostname: `https://${host}`, xslUrl };
512
+
513
+ const siteMapStream = new SitemapStream(sitemapOptions);
514
+ let siteMapSrc = await new Promise((resolve) =>
515
+ streamToPromise(Readable.from(siteMapLinks).pipe(siteMapStream)).then((data) => resolve(data.toString())),
516
+ );
517
+ switch (publicClientId) {
518
+ case 'underpost':
519
+ siteMapSrc = siteMapSrc.replaceAll(
520
+ `</urlset>`,
521
+ `${fs.readFileSync(`./src/client/public/underpost/sitemap-template.txt`, 'utf8')} </urlset>`,
522
+ );
523
+ break;
524
+
525
+ default:
526
+ break;
527
+ }
528
+ // Return a promise that resolves with your XML string
529
+ fs.writeFileSync(`${rootClientPath}/sitemap.xml`, siteMapSrc, 'utf8');
530
+ if (xslUrl)
531
+ fs.writeFileSync(
532
+ `${rootClientPath}/sitemap.xsl`,
533
+ fs.readFileSync(`${rootClientPath}/sitemap`, 'utf8').replaceAll('{{web-url}}', `https://${host}${path}`),
534
+ 'utf8',
535
+ );
536
+
537
+ fs.writeFileSync(
538
+ `${rootClientPath}/robots.txt`,
539
+ `User-agent: *
540
+ Sitemap: https://${host}${path === '/' ? '' : path}/sitemap.xml`,
541
+ 'utf8',
542
+ );
543
+ }
544
+
545
+ if (!enableLiveRebuild && !process.argv.includes('l') && !process.argv.includes('deploy') && docsBuild) {
546
+ // https://www.pullrequest.com/blog/leveraging-jsdoc-for-better-code-documentation-in-javascript/
547
+ // https://jsdoc.app/about-configuring-jsdoc
548
+ // https://jsdoc.app/ Block tags
549
+
550
+ const jsDocsConfig = JSON.parse(fs.readFileSync(`./jsdoc.json`, 'utf8'));
551
+ jsDocsConfig.opts.destination = `./public/${host}${path === '/' ? path : `${path}/`}docs/`;
552
+ jsDocsConfig.opts.theme_opts.title = metadata && metadata.title ? metadata.title : undefined;
553
+ jsDocsConfig.opts.theme_opts.favicon = `./public/${host}${path === '/' ? path : `${path}/favicon.ico`}`;
554
+ fs.writeFileSync(`./jsdoc.json`, JSON.stringify(jsDocsConfig, null, 4), 'utf8');
555
+ logger.warn('build jsdoc view', jsDocsConfig.opts.destination);
556
+ shellExec(`npm run docs`, { silent: true });
557
+
558
+ // coverage
559
+ if (!fs.existsSync(`./coverage`)) {
560
+ shellExec(`npm test`);
561
+ }
562
+ const coverageBuildPath = `${jsDocsConfig.opts.destination}/coverage`;
563
+ fs.mkdirSync(coverageBuildPath, { recursive: true });
564
+ fs.copySync(`./coverage`, coverageBuildPath);
565
+
566
+ // uml
567
+ shellExec(`node bin/deploy uml ${host} ${path}`);
568
+
569
+ // https://swagger-autogen.github.io/docs/
570
+
571
+ const basePath = path === '/' ? `${process.env.BASE_API}` : `/${process.env.BASE_API}`;
572
+
573
+ const doc = {
574
+ info: {
575
+ version: packageData.version, // by default: '1.0.0'
576
+ title: metadata?.title ? `${metadata.title}` : 'REST API', // by default: 'REST API'
577
+ description: metadata?.description ? metadata.description : '', // by default: ''
578
+ },
579
+ servers: [
580
+ {
581
+ url:
582
+ process.env.NODE_ENV === 'development'
583
+ ? `http://localhost:${port}${path}${basePath}`
584
+ : `https://${host}${path}${basePath}`, // by default: 'http://localhost:3000'
585
+ description: `${process.env.NODE_ENV} server`, // by default: ''
586
+ },
587
+ ],
588
+ tags: [
589
+ // by default: empty Array
590
+ {
591
+ name: 'user', // Tag name
592
+ description: 'User API operations', // Tag description
593
+ },
594
+ ],
595
+ components: {
596
+ schemas: {
597
+ userRequest: {
598
+ username: 'user123',
599
+ password: 'Password123',
600
+ email: 'user@example.com',
601
+ },
602
+ userResponse: {
603
+ status: 'success',
604
+ data: {
605
+ token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjY2YzM3N2Y1N2Y5OWU1OTY5YjgxZG...',
606
+ user: {
607
+ _id: '66c377f57f99e5969b81de89',
608
+ email: 'user@example.com',
609
+ emailConfirmed: false,
610
+ username: 'user123',
611
+ role: 'user',
612
+ profileImageId: '66c377f57f99e5969b81de87',
613
+ },
614
+ },
615
+ },
616
+ userUpdateResponse: {
617
+ status: 'success',
618
+ data: {
619
+ _id: '66c377f57f99e5969b81de89',
620
+ email: 'user@example.com',
621
+ emailConfirmed: false,
622
+ username: 'user123222',
623
+ role: 'user',
624
+ profileImageId: '66c377f57f99e5969b81de87',
625
+ },
626
+ },
627
+ userGetResponse: {
628
+ status: 'success',
629
+ data: {
630
+ _id: '66c377f57f99e5969b81de89',
631
+ email: 'user@example.com',
632
+ emailConfirmed: false,
633
+ username: 'user123222',
634
+ role: 'user',
635
+ profileImageId: '66c377f57f99e5969b81de87',
636
+ },
637
+ },
638
+ userLogInRequest: {
639
+ email: 'user@example.com',
640
+ password: 'Password123',
641
+ },
642
+ userBadRequestResponse: {
643
+ status: 'error',
644
+ message: 'Bad request. Please check your inputs, and try again',
645
+ },
646
+ },
647
+ securitySchemes: {
648
+ bearerAuth: {
649
+ type: 'http',
650
+ scheme: 'bearer',
651
+ },
652
+ },
653
+ },
654
+ };
655
+
656
+ // plantuml
657
+ logger.info('copy plantuml', `${rootClientPath}/docs/plantuml`);
658
+ fs.copySync(`./src/client/public/default/plantuml`, `${rootClientPath}/docs/plantuml`);
659
+
660
+ logger.warn('build swagger api docs', doc.info);
661
+
662
+ const outputFile = `./public/${host}${path === '/' ? path : `${path}/`}swagger-output.json`;
663
+ const routes = [];
664
+ for (const api of apis) {
665
+ if (['user'].includes(api)) routes.push(`./src/api/${api}/${api}.router.js`);
666
+ }
667
+
668
+ /* NOTE: If you are using the express Router, you must pass in the 'routes' only the
669
+ root file where the route starts, such as index.js, app.js, routes.js, etc ... */
670
+
671
+ await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
672
+ }
673
+ if (!enableLiveRebuild && process.argv.includes('zip')) {
674
+ logger.warn('build zip', rootClientPath);
675
+
676
+ if (!fs.existsSync('./build')) fs.mkdirSync('./build');
677
+
678
+ const zip = new AdmZip();
679
+ const files = await fs.readdir(rootClientPath, { recursive: true });
680
+
681
+ for (const relativePath of files) {
682
+ const filePath = dir.resolve(`${rootClientPath}/${relativePath}`);
683
+ if (!fs.lstatSync(filePath).isDirectory()) {
684
+ const folder = dir.relative(`public/${host}${path}`, dir.dirname(filePath));
685
+ zip.addLocalFile(filePath, folder);
686
+ }
687
+ }
688
+
689
+ const buildId = `${host}-${path.replaceAll('/', '')}`;
690
+
691
+ logger.warn('write zip', `./build/${buildId}.zip`);
692
+
693
+ zip.writeZip(`./build/${buildId}.zip`);
694
+ }
695
+ }
696
+ }
697
+ };
698
+
699
+ export { buildClient };