underpost 2.8.646 → 2.8.652

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.
@@ -4,13 +4,32 @@ import UnderpostDeploy from './deploy.js';
4
4
  import axios from 'axios';
5
5
  import UnderpostRootEnv from './env.js';
6
6
  import fs from 'fs-extra';
7
+ import { shellExec } from '../server/process.js';
7
8
 
8
9
  const logger = loggerFactory(import.meta);
9
10
 
10
11
  class UnderpostMonitor {
11
12
  static API = {
12
- async callback(deployId, env = 'development', options = { now: false, single: false, msInterval: '' }) {
13
- const router = await UnderpostDeploy.API.routerFactory(deployId, env);
13
+ async callback(
14
+ deployId,
15
+ env = 'development',
16
+ options = { now: false, single: false, msInterval: '', type: '' },
17
+ commanderOptions,
18
+ auxRouter,
19
+ ) {
20
+ if (deployId === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`)) {
21
+ for (const _deployId of fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(','))
22
+ UnderpostMonitor.API.callback(
23
+ _deployId.trim(),
24
+ env,
25
+ options,
26
+ commanderOptions,
27
+ await UnderpostDeploy.API.routerFactory(_deployId, env),
28
+ );
29
+ return;
30
+ }
31
+
32
+ const router = auxRouter ?? (await UnderpostDeploy.API.routerFactory(deployId, env));
14
33
 
15
34
  const confServer = loadReplicas(
16
35
  JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
@@ -19,20 +38,30 @@ class UnderpostMonitor {
19
38
 
20
39
  const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
21
40
 
22
- logger.info('', pathPortAssignmentData);
41
+ logger.info(`${deployId} ${env}`, pathPortAssignmentData);
23
42
 
24
- const errorPayloads = [];
43
+ let errorPayloads = [];
44
+ let traffic = 'blue';
25
45
  const maxAttempts = Object.keys(pathPortAssignmentData)
26
46
  .map((host) => pathPortAssignmentData[host].length)
27
47
  .reduce((accumulator, value) => accumulator + value, 0);
28
48
 
29
49
  const monitor = async (reject) => {
30
- logger.info('Check server health');
50
+ logger.info(`[${deployId}-${env}] Check server health`);
31
51
  for (const host of Object.keys(pathPortAssignmentData)) {
32
52
  for (const instance of pathPortAssignmentData[host]) {
33
53
  const { port, path } = instance;
34
54
  if (path.match('peer') || path.match('socket')) continue;
35
- const urlTest = `http://localhost:${port}${path}`;
55
+ let urlTest = `http://localhost:${port}${path}`;
56
+ switch (options.type) {
57
+ case 'remote':
58
+ case 'blue-green':
59
+ urlTest = `https://${host}${path}`;
60
+ break;
61
+
62
+ default:
63
+ break;
64
+ }
36
65
  // logger.info('Test instance', urlTest);
37
66
  await axios.get(urlTest, { timeout: 10000 }).catch((error) => {
38
67
  // console.log(error);
@@ -50,8 +79,42 @@ class UnderpostMonitor {
50
79
  errorPayloads.push(errorPayload);
51
80
  if (errorPayloads.length >= maxAttempts) {
52
81
  const message = JSON.stringify(errorPayloads, null, 4);
53
- if (reject) reject(message);
54
- else throw new Error(message);
82
+ logger.error(
83
+ `Deployment ${deployId} ${env} has been reached max attempts error payloads`,
84
+ errorPayloads,
85
+ );
86
+ switch (options.type) {
87
+ case 'blue-green':
88
+ {
89
+ const confServer = JSON.parse(
90
+ fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'),
91
+ );
92
+
93
+ for (const host of Object.keys(confServer)) {
94
+ shellExec(`sudo kubectl delete HTTPProxy ${host}`);
95
+ }
96
+ shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${traffic}`);
97
+
98
+ if (traffic === 'blue') traffic = 'green';
99
+ else traffic = 'blue';
100
+
101
+ shellExec(
102
+ `node bin deploy --info-router --build-manifest --traffic ${traffic} ${deployId} ${env}`,
103
+ );
104
+
105
+ shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml`);
106
+ }
107
+
108
+ break;
109
+
110
+ case 'remote':
111
+ break;
112
+
113
+ default:
114
+ if (reject) reject(message);
115
+ else throw new Error(message);
116
+ }
117
+ errorPayloads = [];
55
118
  }
56
119
  logger.error('Error accumulator', errorPayloads.length);
57
120
  }
@@ -63,11 +126,47 @@ class UnderpostMonitor {
63
126
  if (options.single === true) return;
64
127
  let optionsMsTimeout = parseInt(options.msInterval);
65
128
  if (isNaN(optionsMsTimeout)) optionsMsTimeout = 30000;
129
+ let monitorTrafficName;
130
+ let monitorPodName;
66
131
  const monitorCallBack = (resolve, reject) => {
67
- const envMsTimeout = UnderpostRootEnv.API.get('monitor-ms');
132
+ const envMsTimeout = UnderpostRootEnv.API.get(`${deployId}-${env}-monitor-ms`);
68
133
  setTimeout(
69
134
  async () => {
70
- switch (UnderpostRootEnv.API.get('monitor-input')) {
135
+ switch (options.type) {
136
+ case 'blue-green':
137
+ {
138
+ if (monitorTrafficName !== traffic) {
139
+ monitorTrafficName = undefined;
140
+ monitorPodName = undefined;
141
+ }
142
+ const cmd = `underpost config get container-status`;
143
+ const checkDeploymentReadyStatus = () => {
144
+ const pods = UnderpostDeploy.API.get(`${deployId}-${env}-${traffic}`);
145
+ if (pods && pods[0]) {
146
+ const { NAME } = pods[0];
147
+ if (
148
+ shellExec(`sudo kubectl exec -i ${NAME} -- sh -c "${cmd}"`, { stdout: true }).match(
149
+ `${deployId}-${env}-running-deployment`,
150
+ )
151
+ ) {
152
+ monitorPodName = NAME;
153
+ monitorTrafficName = `${traffic}`;
154
+ }
155
+ }
156
+ };
157
+ if (!monitorPodName) {
158
+ checkDeploymentReadyStatus();
159
+ monitorCallBack(resolve, reject);
160
+ return;
161
+ }
162
+ }
163
+
164
+ break;
165
+
166
+ default:
167
+ break;
168
+ }
169
+ switch (UnderpostRootEnv.API.get(`${deployId}-${env}-monitor-input`)) {
71
170
  case 'pause':
72
171
  monitorCallBack(resolve, reject);
73
172
  return;
@@ -84,7 +183,7 @@ class UnderpostMonitor {
84
183
  !isNaN(envMsTimeout) ? envMsTimeout : optionsMsTimeout,
85
184
  );
86
185
  };
87
- return await new Promise((...args) => monitorCallBack(...args));
186
+ return new Promise((...args) => monitorCallBack(...args));
88
187
  },
89
188
  };
90
189
  }
@@ -204,7 +204,7 @@ const Account = {
204
204
  disabled: false,
205
205
  extension: async () =>
206
206
  html`${await BtnIcon.Render({
207
- class: `wfa btn-input-extension btn-account-update-username`,
207
+ class: `in wfa btn-input-extension btn-account-update-username`,
208
208
  type: 'button',
209
209
  style: 'text-align: left',
210
210
  label: html`${Translate.Render(`update`)}`,
@@ -223,7 +223,7 @@ const Account = {
223
223
  extension: !(options && options.disabled && options.disabled.includes('emailConfirm'))
224
224
  ? async () => html`<div class="in verify-email-status"></div>
225
225
  ${await BtnIcon.Render({
226
- class: `wfa btn-input-extension btn-confirm-email`,
226
+ class: `in wfa btn-input-extension btn-confirm-email`,
227
227
  type: 'button',
228
228
  style: 'text-align: left',
229
229
  label: html`<div class="in">
@@ -246,7 +246,7 @@ const Account = {
246
246
  disabledEye: true,
247
247
  extension: async () =>
248
248
  html`${await BtnIcon.Render({
249
- class: `wfa btn-input-extension btn-account-change-password`,
249
+ class: `in wfa btn-input-extension btn-account-change-password`,
250
250
  type: 'button',
251
251
  style: 'text-align: left',
252
252
  label: html`${Translate.Render(`change-password`)}`,
@@ -761,7 +761,6 @@ const renderWave = ({ id }) => {
761
761
  const cssTokensEffect = {};
762
762
  const cssTokensContainer = {};
763
763
  const cssEffect = async (containerSelector, event) => {
764
- return;
765
764
  // Array.from(event.target.classList)
766
765
  let offsetX, offsetY;
767
766
  if (Array.from(event.srcElement.classList).includes('ripple') && cssTokensContainer[containerSelector]) {
@@ -524,6 +524,7 @@ const CssCoreDark = {
524
524
  margin: 5px 0 0 0;
525
525
  padding: 5px;
526
526
  font-size: 16px;
527
+ min-height: 40px;
527
528
  }
528
529
  .btn-input-extension:hover {
529
530
  }
@@ -843,6 +844,7 @@ const CssCoreLight = {
843
844
  margin: 5px 0 0 0;
844
845
  padding: 5px;
845
846
  font-size: 16px;
847
+ min-height: 40px;
846
848
  }
847
849
  .btn-input-extension:hover {
848
850
  }
@@ -12,7 +12,7 @@ const EventsUI = {
12
12
  if (!s(id)) return;
13
13
  let complete = true;
14
14
  s(id)[type] = async function (e) {
15
- cssEffect(id, e);
15
+ if (options.clickEffect) cssEffect(id, e);
16
16
  if (complete) {
17
17
  complete = false;
18
18
  await LoadingAnimation.spinner.play(loadingContainer ? loadingContainer : id);
@@ -7,28 +7,18 @@ const RichText = {
7
7
  Render: async function (options = { id: '', parentIdModal: '' }) {
8
8
  const id = options?.id ? options.id : getId(this.Tokens, 'rich-text-');
9
9
  this.Tokens[id] = {};
10
- let top, height;
11
10
  setTimeout(() => {
12
11
  const easyMDE = new EasyMDE({
13
12
  element: s(`.${id}`),
13
+ hideIcons: ['fullscreen', 'side-by-side'],
14
14
  onToggleFullScreen: (onFs) => {
15
15
  if (onFs) {
16
16
  if (options.parentIdModal) {
17
- s(`.btn-bar-modal-container-${options.parentIdModal}`).classList.add('hide');
18
- top = newInstance(s(`.${options.parentIdModal}`).style.top);
19
- height = newInstance(s(`.${options.parentIdModal}`).style.height);
20
- s(`.${options.parentIdModal}`).style.top = '0px';
21
- s(`.${options.parentIdModal}`).style.height = `${window.innerHeight}px`;
22
17
  }
23
18
  // Modal.cleanUI();
24
- if (s(`.slide-menu-top-bar`)) s(`.slide-menu-top-bar`).classList.add('hide');
25
19
  } else {
26
20
  if (options.parentIdModal) {
27
- s(`.btn-bar-modal-container-${options.parentIdModal}`).classList.remove('hide');
28
- s(`.${options.parentIdModal}`).style.top = top;
29
- s(`.${options.parentIdModal}`).style.height = height;
30
21
  }
31
- if (s(`.slide-menu-top-bar`)) s(`.slide-menu-top-bar`).classList.add('remove');
32
22
  // Modal.restoreUI();
33
23
  }
34
24
  },
package/src/index.js CHANGED
@@ -30,7 +30,7 @@ class Underpost {
30
30
  * @type {String}
31
31
  * @memberof Underpost
32
32
  */
33
- static version = 'v2.8.646';
33
+ static version = 'v2.8.652';
34
34
  /**
35
35
  * Repository cli API
36
36
  * @static
@@ -32,6 +32,9 @@ const MailerProvider = {
32
32
  },
33
33
  ) {
34
34
  try {
35
+ options.transport.tls = {
36
+ rejectUnauthorized: false,
37
+ };
35
38
  const { id } = options;
36
39
  // Generate test SMTP service account from ethereal.email
37
40
  // Only needed if you don't have a real mail account for testing
@@ -683,6 +683,19 @@ Sitemap: https://${host}${path === '/' ? '' : path}/sitemap.xml`,
683
683
  root file where the route starts, such as index.js, app.js, routes.js, etc ... */
684
684
 
685
685
  await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
686
+
687
+ const htmlFiles = await fs.readdir(`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}`);
688
+ for (const htmlFile of htmlFiles) {
689
+ if (htmlFile.match('.html')) {
690
+ fs.writeFileSync(
691
+ `./public/${host}/docs/engine/${Underpost.version.replace('v', '')}/${htmlFile}`,
692
+ fs
693
+ .readFileSync(`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}/${htmlFile}`, 'utf8')
694
+ .replaceAll('Tutorials', 'References'),
695
+ 'utf8',
696
+ );
697
+ }
698
+ }
686
699
  }
687
700
 
688
701
  if (client) {
@@ -923,6 +923,49 @@ const mergeFile = async (parts = [], outputFilePath) => {
923
923
  });
924
924
  };
925
925
 
926
+ const rebuildConfFactory = ({ deployId, valkey, mongo }) => {
927
+ const confServer = loadReplicas(
928
+ JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
929
+ );
930
+ for (const host of Object.keys(confServer)) {
931
+ for (const path of Object.keys(confServer[host])) {
932
+ if (!confServer[host][path].db) continue;
933
+ const { singleReplica, replicas, db } = confServer[host][path];
934
+ const { provider } = db;
935
+ if (singleReplica) {
936
+ for (const replica of replicas) {
937
+ const deployIdReplica = buildReplicaId({ replica, deployId });
938
+ const confServerReplica = JSON.parse(
939
+ fs.readFileSync(`./engine-private/replica/${deployIdReplica}/conf.server.json`, 'utf8'),
940
+ );
941
+ for (const _host of Object.keys(confServerReplica)) {
942
+ for (const _path of Object.keys(confServerReplica[_host])) {
943
+ confServerReplica[_host][_path].valkey = valkey;
944
+ switch (provider) {
945
+ case 'mongoose':
946
+ confServerReplica[_host][_path].db.host = mongo.host;
947
+ break;
948
+ }
949
+ }
950
+ }
951
+ fs.writeFileSync(
952
+ `./engine-private/replica/${deployIdReplica}/conf.server.json`,
953
+ JSON.stringify(confServerReplica, null, 4),
954
+ 'utf8',
955
+ );
956
+ }
957
+ }
958
+ confServer[host][path].valkey = valkey;
959
+ switch (provider) {
960
+ case 'mongoose':
961
+ confServer[host][path].db.host = mongo.host;
962
+ break;
963
+ }
964
+ }
965
+ }
966
+ fs.writeFileSync(`./engine-private/conf/${deployId}/conf.server.json`, JSON.stringify(confServer, null, 4), 'utf8');
967
+ };
968
+
926
969
  const getRestoreCronCmd = async (options = { host: '', path: '', conf: {}, deployId: '' }) => {
927
970
  const { host, path, conf, deployId } = options;
928
971
  const { runtime, db, git, directory } = conf[host][path];
@@ -1166,4 +1209,5 @@ export {
1166
1209
  pathPortAssignmentFactory,
1167
1210
  deployRangePortFactory,
1168
1211
  awaitDeployMonitor,
1212
+ rebuildConfFactory,
1169
1213
  };
@@ -21,6 +21,7 @@ import { Lampp } from '../runtime/lampp/Lampp.js';
21
21
  import { getDeployId } from './conf.js';
22
22
  import { JSONweb, ssrFactory } from './client-formatted.js';
23
23
  import Underpost from '../index.js';
24
+ import { createValkeyConnection } from './valkey.js';
24
25
 
25
26
  dotenv.config();
26
27
 
@@ -67,6 +68,7 @@ const buildRuntime = async () => {
67
68
  peer,
68
69
  singleReplica,
69
70
  replicas,
71
+ valkey,
70
72
  } = confServer[host][path];
71
73
 
72
74
  if (singleReplica && replicas && replicas.length > 0 && !singleReplicaHosts.includes(host)) {
@@ -361,6 +363,9 @@ const buildRuntime = async () => {
361
363
 
362
364
  if (db && apis) await DataBaseProvider.load({ apis, host, path, db });
363
365
 
366
+ // valkey server
367
+ await createValkeyConnection({ host, path }, valkey);
368
+
364
369
  if (mailer) {
365
370
  const mailerSsrConf = confSSR[getCapVariableName(client)];
366
371
  await MailerProvider.load({
@@ -4,6 +4,7 @@ import fs from 'fs-extra';
4
4
  import { awaitDeployMonitor } from './conf.js';
5
5
  import { actionInitLog, loggerFactory } from './logger.js';
6
6
  import { shellCd, shellExec } from './process.js';
7
+ import UnderpostRootEnv from '../cli/env.js';
7
8
 
8
9
  const logger = loggerFactory(import.meta);
9
10
 
@@ -20,14 +21,20 @@ class UnderpostStartUp {
20
21
  },
21
22
  listenServerFactory: (logic = async () => {}) => {
22
23
  return {
23
- listen: async (...args) => (
24
- setTimeout(() => {
25
- const message = 'Listen server factory timeout';
26
- logger.error(message);
27
- throw new Error(message);
28
- }, 80000000), // ~ 55 days
29
- (logic ? await logic(...args) : undefined, args[1]())
30
- ),
24
+ listen: async (...args) => {
25
+ const msDelta = 1000;
26
+ const msMax = 30 * 24 * 60 * 60 * 1000; // ~ 1 month
27
+ let msCount = 0;
28
+ setInterval(() => {
29
+ msCount += msDelta;
30
+ if (msCount >= msMax) {
31
+ const message = 'Listen server factory timeout';
32
+ logger.error(message);
33
+ throw new Error(message);
34
+ }
35
+ }, msDelta);
36
+ return logic ? await logic(...args) : undefined, args[1]();
37
+ },
31
38
  };
32
39
  },
33
40
  listenPortController: async (server, port, metadata) =>
@@ -108,7 +115,7 @@ class UnderpostStartUp {
108
115
  shellExec(`node bin/deploy conf ${deployId} ${env}`);
109
116
  shellExec(`npm ${runCmd} deploy deploy-id:${deployId}`, { async: true });
110
117
  await awaitDeployMonitor(true);
111
- return await UnderpostMonitor.API.callback(deployId, env);
118
+ UnderpostRootEnv.API.set('container-status', `${deployId}-${env}-running-deployment`);
112
119
  },
113
120
  };
114
121
  }
@@ -5,12 +5,24 @@ import { loggerFactory } from './logger.js';
5
5
 
6
6
  const logger = loggerFactory(import.meta);
7
7
 
8
+ const ValkeyInstances = {};
9
+
8
10
  let valkeyEnabled = true;
9
11
 
10
12
  const disableValkeyErrorMessage = 'valkey is not enabled';
11
13
 
12
14
  const isValkeyEnable = () => valkeyEnabled;
13
15
 
16
+ const createValkeyConnection = async (
17
+ instance = { host: '', port: 0 },
18
+ valkeyServerConnectionOptions = { host: '', port: 0 },
19
+ ) => {
20
+ ValkeyInstances[`${instance.host}${instance.path}`] = await ValkeyAPI.valkeyClientFactory(
21
+ valkeyServerConnectionOptions,
22
+ );
23
+ return ValkeyInstances[`${instance.host}${instance.path}`];
24
+ };
25
+
14
26
  const selectDtoFactory = (payload, select) => {
15
27
  const result = {};
16
28
  for (const key of Object.keys(select)) {
@@ -19,10 +31,12 @@ const selectDtoFactory = (payload, select) => {
19
31
  return result;
20
32
  };
21
33
 
22
- const valkeyClientFactory = async () => {
34
+ const valkeyClientFactory = async (options) => {
23
35
  const valkey = new Valkey({
24
36
  // port: 6379,
25
37
  // host: 'service-valkey.default.svc.cluster.local',
38
+ port: options?.port ? options.port : undefined,
39
+ host: options?.port ? options.host : undefined,
26
40
  retryStrategy: (attempt) => {
27
41
  if (attempt === 1) {
28
42
  valkey.disconnect();
@@ -46,12 +60,12 @@ const valkeyClientFactory = async () => {
46
60
  return valkey;
47
61
  };
48
62
 
49
- const getValkeyObject = async (key = '') => {
63
+ const getValkeyObject = async (options = { host: '', port: 0 }, key = '') => {
50
64
  if (!valkeyEnabled) {
51
65
  logger.warn(disableValkeyErrorMessage + ' get', key);
52
66
  return null;
53
67
  }
54
- const object = await valkey.get(key);
68
+ const object = await ValkeyInstances[`${options.host}${options.path}`].get(key);
55
69
  try {
56
70
  return JSON.parse(object);
57
71
  } catch (error) {
@@ -60,19 +74,19 @@ const getValkeyObject = async (key = '') => {
60
74
  }
61
75
  };
62
76
 
63
- const setValkeyObject = async (key = '', payload = {}) => {
77
+ const setValkeyObject = async (options = { host: '', port: 0 }, key = '', payload = {}) => {
64
78
  if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
65
- return await valkey.set(key, JSON.stringify(payload));
79
+ return await ValkeyInstances[`${options.host}${options.path}`].set(key, JSON.stringify(payload));
66
80
  };
67
81
 
68
- const updateValkeyObject = async (key = '', payload = {}) => {
82
+ const updateValkeyObject = async (options = { host: '', port: 0 }, key = '', payload = {}) => {
69
83
  if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
70
- const object = await getValkeyObject(key, valkey);
84
+ const object = await getValkeyObject(key);
71
85
  object.updatedAt = new Date().toISOString();
72
- return await valkey.set(key, JSON.stringify({ ...object, ...payload }));
86
+ return await ValkeyInstances[`${options.host}${options.path}`].set(key, JSON.stringify({ ...object, ...payload }));
73
87
  };
74
88
 
75
- const valkeyObjectFactory = async (module = '', options = { host: 'localhost', object: {} }) => {
89
+ const valkeyObjectFactory = async (options = { host: 'localhost', object: {} }, module = '') => {
76
90
  if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
77
91
  const idoDate = new Date().toISOString();
78
92
  options.object = options.object || {};
@@ -112,10 +126,9 @@ const ValkeyAPI = {
112
126
  setValkeyObject,
113
127
  valkeyObjectFactory,
114
128
  updateValkeyObject,
129
+ createValkeyConnection,
115
130
  };
116
131
 
117
- const valkey = await ValkeyAPI.valkeyClientFactory();
118
-
119
132
  export {
120
133
  valkeyClientFactory,
121
134
  selectDtoFactory,
@@ -124,5 +137,6 @@ export {
124
137
  valkeyObjectFactory,
125
138
  updateValkeyObject,
126
139
  isValkeyEnable,
140
+ createValkeyConnection,
127
141
  ValkeyAPI,
128
142
  };