@underpostnet/underpost 2.8.4 → 2.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.github/workflows/ghpkg.yml +12 -45
  2. package/.github/workflows/npmpkg.yml +67 -0
  3. package/.github/workflows/publish.yml +5 -5
  4. package/.github/workflows/pwa-microservices-template.page.yml +2 -1
  5. package/.github/workflows/pwa-microservices-template.test.yml +2 -2
  6. package/.vscode/settings.json +10 -1
  7. package/CHANGELOG.md +40 -0
  8. package/Dockerfile +6 -27
  9. package/bin/build.js +73 -165
  10. package/bin/deploy.js +76 -101
  11. package/bin/file.js +29 -15
  12. package/bin/hwt.js +0 -10
  13. package/bin/index.js +191 -32
  14. package/bin/util.js +0 -15
  15. package/bin/vs.js +1 -0
  16. package/conf.js +0 -2
  17. package/docker-compose.yml +1 -1
  18. package/manifests/kind-config-dev.yaml +12 -0
  19. package/manifests/{core/underpost-engine-mongodb-backup-cronjob.yaml → mongodb/backup-cronjob.yaml} +14 -12
  20. package/manifests/mongodb/kustomization.yaml +11 -0
  21. package/manifests/mongodb/pv-pvc.yaml +23 -0
  22. package/manifests/{core/underpost-engine-statefulset.yaml → mongodb/statefulset.yaml} +34 -0
  23. package/manifests/mongodb-4.4/kustomization.yaml +7 -0
  24. package/manifests/mongodb-4.4/service-deployment.yaml +63 -0
  25. package/manifests/valkey/kustomization.yaml +2 -2
  26. package/package.json +22 -4
  27. package/src/api/core/core.service.js +1 -1
  28. package/src/cli/cluster.js +202 -0
  29. package/src/cli/cron.js +90 -0
  30. package/src/cli/db.js +212 -0
  31. package/src/cli/deploy.js +318 -0
  32. package/src/cli/env.js +52 -0
  33. package/src/cli/fs.js +149 -0
  34. package/src/cli/image.js +148 -0
  35. package/src/cli/repository.js +125 -0
  36. package/src/cli/script.js +53 -0
  37. package/src/cli/secrets.js +37 -0
  38. package/src/cli/test.js +118 -0
  39. package/src/client/components/core/Auth.js +22 -4
  40. package/src/client/components/core/CalendarCore.js +12 -1
  41. package/src/client/components/core/CommonJs.js +134 -2
  42. package/src/client/components/core/Css.js +1 -0
  43. package/src/client/components/core/CssCore.js +2 -4
  44. package/src/client/components/core/Docs.js +1 -2
  45. package/src/client/components/core/Input.js +5 -3
  46. package/src/client/components/core/LoadingAnimation.js +8 -1
  47. package/src/client/components/core/Modal.js +30 -7
  48. package/src/client/components/core/Panel.js +8 -6
  49. package/src/client/components/core/PanelForm.js +23 -7
  50. package/src/client/components/core/Scroll.js +1 -0
  51. package/src/client/components/core/Translate.js +4 -0
  52. package/src/client/components/core/VanillaJs.js +0 -9
  53. package/src/client/components/core/Worker.js +34 -31
  54. package/src/client/services/core/core.service.js +15 -10
  55. package/src/client/ssr/Render.js +4 -1
  56. package/src/client/ssr/body/CacheControl.js +2 -3
  57. package/src/client/sw/default.sw.js +3 -3
  58. package/src/db/mongo/MongooseDB.js +17 -1
  59. package/src/index.js +85 -26
  60. package/src/server/backup.js +49 -93
  61. package/src/server/client-build.js +33 -33
  62. package/src/server/client-formatted.js +6 -3
  63. package/src/server/conf.js +82 -199
  64. package/src/server/dns.js +29 -53
  65. package/src/server/downloader.js +0 -8
  66. package/src/server/logger.js +7 -7
  67. package/src/server/network.js +17 -7
  68. package/src/server/runtime.js +24 -23
  69. package/test/api.test.js +0 -8
  70. package/manifests/core/kustomization.yaml +0 -11
  71. package/src/dns.js +0 -22
  72. package/src/server/project.js +0 -39
  73. package/startup.cjs +0 -12
  74. /package/manifests/deployment/{mongo-express.yaml → mongo-express/deployment.yaml} +0 -0
  75. /package/manifests/deployment/{phpmyadmin.yaml → phpmyadmin/deployment.yaml} +0 -0
  76. /package/manifests/{core/underpost-engine-backup-access.yaml → mongodb/backup-access.yaml} +0 -0
  77. /package/manifests/{core/underpost-engine-backup-pv-pvc.yaml → mongodb/backup-pv-pvc.yaml} +0 -0
  78. /package/manifests/{core/underpost-engine-mongodb-configmap.yaml → mongodb/configmap.yaml} +0 -0
  79. /package/manifests/{core/underpost-engine-headless-service.yaml → mongodb/headless-service.yaml} +0 -0
  80. /package/manifests/{core/underpost-engine-pv-pvc.yaml → mongodb-4.4/pv-pvc.yaml} +0 -0
  81. /package/manifests/valkey/{underpost-engine-valkey-service.yaml → service.yaml} +0 -0
  82. /package/manifests/valkey/{underpost-engine-valkey-statefulset.yaml → statefulset.yaml} +0 -0
package/src/cli/fs.js ADDED
@@ -0,0 +1,149 @@
1
+ import { v2 as cloudinary } from 'cloudinary';
2
+ import { loggerFactory } from '../server/logger.js';
3
+ import dotenv from 'dotenv';
4
+ import AdmZip from 'adm-zip';
5
+ import * as dir from 'path';
6
+ import fs from 'fs-extra';
7
+ import { Downloader } from '../server/downloader.js';
8
+ import UnderpostRepository from './repository.js';
9
+ import { shellExec } from '../server/process.js';
10
+ dotenv.config();
11
+
12
+ const logger = loggerFactory(import.meta);
13
+
14
+ class UnderpostFileStorage {
15
+ static API = {
16
+ cloudinaryConfig() {
17
+ // https://console.cloudinary.com/
18
+ cloudinary.config({
19
+ cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
20
+ api_key: process.env.CLOUDINARY_API_KEY,
21
+ api_secret: process.env.CLOUDINARY_API_SECRET,
22
+ });
23
+ },
24
+ getStorageConf(options) {
25
+ let storage, storageConf;
26
+ if (options.deployId && typeof options.deployId === 'string') {
27
+ storageConf = `./engine-private/conf/${options.deployId}/storage.json`;
28
+ if (!fs.existsSync(storageConf)) fs.writeFileSync(storageConf, JSON.stringify({}), 'utf8');
29
+ storage = JSON.parse(fs.readFileSync(storageConf, 'utf8'));
30
+ }
31
+ return { storage, storageConf };
32
+ },
33
+ writeStorageConf(storage, storageConf) {
34
+ if (storage) fs.writeFileSync(storageConf, JSON.stringify(storage, null, 4), 'utf8');
35
+ },
36
+ async recursiveCallback(
37
+ path,
38
+ options = { rm: false, recursive: false, deployId: '', force: false, pull: false, git: false },
39
+ ) {
40
+ const { storage, storageConf } = UnderpostFileStorage.API.getStorageConf(options);
41
+ const deleteFiles = UnderpostRepository.API.getDeleteFiles(path);
42
+ for (const relativePath of deleteFiles) {
43
+ const _path = path + '/' + relativePath;
44
+ if (_path in storage) {
45
+ await UnderpostFileStorage.API.delete(_path);
46
+ delete storage[_path];
47
+ }
48
+ }
49
+ const files =
50
+ options.git === true
51
+ ? UnderpostRepository.API.getChangedFiles(path)
52
+ : await fs.readdir(path, { recursive: true });
53
+ if (options.pull === true) {
54
+ for (const _path of Object.keys(storage)) {
55
+ if (!fs.existsSync(_path) || options.force === true) {
56
+ if (options.force === true && fs.existsSync(_path)) fs.removeSync(_path);
57
+ await UnderpostFileStorage.API.pull(_path, options);
58
+ } else logger.warn(`Pull path already exists`, _path);
59
+ }
60
+ } else
61
+ for (const relativePath of files) {
62
+ const _path = path + '/' + relativePath;
63
+ if (fs.statSync(_path).isDirectory()) {
64
+ if (options.pull === true && !fs.existsSync(_path)) fs.mkdirSync(_path, { recursive: true });
65
+ continue;
66
+ } else if (!(_path in storage) || options.force === true) {
67
+ await UnderpostFileStorage.API.upload(_path, options);
68
+ if (storage) storage[_path] = {};
69
+ } else logger.warn('File already exists', _path);
70
+ }
71
+ UnderpostFileStorage.API.writeStorageConf(storage, storageConf);
72
+ if (options.git === true) {
73
+ shellExec(`cd ${path} && git add .`);
74
+ shellExec(`underpost cmt ${path} feat`);
75
+ }
76
+ },
77
+ async callback(
78
+ path,
79
+ options = { rm: false, recursive: false, deployId: '', force: false, pull: false, git: false },
80
+ ) {
81
+ if (options.recursive === true || options.git === true)
82
+ return await UnderpostFileStorage.API.recursiveCallback(path, options);
83
+ if (options.pull === true) return await UnderpostFileStorage.API.pull(path, options);
84
+ if (options.rm === true) return await UnderpostFileStorage.API.delete(path, options);
85
+ return await UnderpostFileStorage.API.upload(path, options);
86
+ },
87
+ async upload(path, options = { rm: false, recursive: false, deployId: '', force: false, pull: false }) {
88
+ UnderpostFileStorage.API.cloudinaryConfig();
89
+ const { storage, storageConf } = UnderpostFileStorage.API.getStorageConf(options);
90
+ // path = UnderpostFileStorage.API.file2Zip(path);
91
+ const uploadResult = await cloudinary.uploader
92
+ .upload(path, {
93
+ public_id: path,
94
+ resource_type: 'raw',
95
+ overwrite: options.force === true ? true : false,
96
+ })
97
+ .catch((error) => {
98
+ logger.error(error, { path, stack: error.stack });
99
+ });
100
+ logger.info('upload result', uploadResult);
101
+ if (storage) storage[path] = {};
102
+ UnderpostFileStorage.API.writeStorageConf(storage, storageConf);
103
+ return uploadResult;
104
+ },
105
+ async pull(path) {
106
+ UnderpostFileStorage.API.cloudinaryConfig();
107
+ const folder = dir.dirname(path);
108
+ if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
109
+ const downloadResult = await cloudinary.utils.download_archive_url({
110
+ public_ids: [path],
111
+ resource_type: 'raw',
112
+ });
113
+ logger.info('download result', downloadResult);
114
+ await Downloader(downloadResult, path + '.zip');
115
+ path = UnderpostFileStorage.API.zip2File(path + '.zip');
116
+ fs.removeSync(path + '.zip');
117
+ },
118
+ async delete(path) {
119
+ UnderpostFileStorage.API.cloudinaryConfig();
120
+ const deleteResult = await cloudinary.api
121
+ .delete_resources([path], { type: 'upload', resource_type: 'raw' })
122
+ .catch((error) => {
123
+ logger.error(error, { path, stack: error.stack });
124
+ });
125
+ logger.info('delete result', deleteResult);
126
+ return deleteResult;
127
+ },
128
+ file2Zip(path) {
129
+ const zip = new AdmZip();
130
+ zip.addLocalFile(path, '/');
131
+ path = path + '.zip';
132
+ zip.writeZip(path);
133
+ return path;
134
+ },
135
+ zip2File(path) {
136
+ const zip = new AdmZip(path);
137
+ path = path.replaceAll('.zip', '');
138
+ zip.extractEntryTo(
139
+ /*entry name*/ path.split('/').pop(),
140
+ /*target path*/ dir.dirname(path),
141
+ /*maintainEntryPath*/ false,
142
+ /*overwrite*/ true,
143
+ );
144
+ return path;
145
+ },
146
+ };
147
+ }
148
+
149
+ export default UnderpostFileStorage;
@@ -0,0 +1,148 @@
1
+ import fs from 'fs-extra';
2
+ import Underpost from '../index.js';
3
+ import { shellCd, shellExec } from '../server/process.js';
4
+ import dotenv from 'dotenv';
5
+ import { getNpmRootPath } from '../server/conf.js';
6
+ import { timer } from '../client/components/core/CommonJs.js';
7
+ import UnderpostRootEnv from './env.js';
8
+
9
+ dotenv.config();
10
+
11
+ class UnderpostImage {
12
+ static API = {
13
+ dockerfile: {
14
+ pullBaseImages() {
15
+ shellExec(`sudo podman pull docker.io/library/debian:buster`);
16
+ },
17
+ build(
18
+ deployId = 'default',
19
+ env = 'development',
20
+ path = '.',
21
+ options = { imageArchive: false, podmanSave: false, imageName: '', imageVersion: '' },
22
+ ) {
23
+ const imgName = `${
24
+ options.imageName && typeof options.imageName === 'string' ? options.imageName : `${deployId}-${env}`
25
+ }:${
26
+ options.imageVersion && typeof options.imageVersions === 'string' ? options.imageVersion : Underpost.version
27
+ }`;
28
+ const podManImg = `localhost/${imgName}`;
29
+ const imagesStoragePath = `/images`;
30
+ if (!fs.existsSync(`${path}${imagesStoragePath}`))
31
+ fs.mkdirSync(`${path}${imagesStoragePath}`, { recursive: true });
32
+ const tarFile = `.${imagesStoragePath}/${imgName.replace(':', '_')}.tar`;
33
+
34
+ let secrets = ' ';
35
+ let secretDockerInput = '';
36
+
37
+ const envObj = dotenv.parse(fs.readFileSync(`${getNpmRootPath()}/underpost/.env`, 'utf8'));
38
+
39
+ for (const key of Object.keys(envObj)) {
40
+ continue;
41
+ secrets += ` && export ${key}="${envObj[key]}" `; // $(cat gitlab-token.txt)
42
+ secretDockerInput += ` --secret id=${key},env=${key} \ `;
43
+ }
44
+ // --rm --no-cache
45
+ if (options.imageArchive !== true) {
46
+ fs.copyFile(`${getNpmRootPath()}/underpost/.env`, `${path}/.env.underpost`);
47
+ shellExec(
48
+ `cd ${path}${secrets}&& sudo podman build -f ./Dockerfile -t ${imgName} --pull=never --cap-add=CAP_AUDIT_WRITE${secretDockerInput}`,
49
+ );
50
+ fs.removeSync(`${path}/.env.underpost`);
51
+ }
52
+ if (options.imageArchive !== true || options.podmanSave === true)
53
+ shellExec(`cd ${path} && podman save -o ${tarFile} ${podManImg}`);
54
+ shellExec(`cd ${path} && sudo kind load image-archive ${tarFile}`);
55
+ },
56
+ async script(deployId = 'default', env = 'development', options = { run: false, build: false }) {
57
+ if (deployId === 'deploy') {
58
+ const _deployId = UnderpostRootEnv.API.get('deploy-id');
59
+ const _env = UnderpostRootEnv.API.get('deploy-env');
60
+ const _path = UnderpostRootEnv.API.get('deploy-path');
61
+ if (_deployId) {
62
+ deployId = _deployId;
63
+ if (_env) env = _env;
64
+ if (_path) path = _path;
65
+ } else {
66
+ await timer(30 * 1000);
67
+ return await UnderpostImage.API.script(deployId, env, path, options);
68
+ }
69
+ }
70
+ if (options.build === true) {
71
+ const buildBasePath = `/home/dd`;
72
+ const repoName = `engine-${deployId.split('-')[1]}`;
73
+ fs.mkdirSync(buildBasePath, { recursive: true });
74
+ shellExec(`cd ${buildBasePath} && underpost clone underpostnet/${repoName}`);
75
+ shellExec(`cd ${buildBasePath} && sudo mv ./${repoName} ./engine`);
76
+ shellExec(`cd ${buildBasePath}/engine && underpost clone underpostnet/${repoName}-private`);
77
+ shellExec(`cd ${buildBasePath}/engine && sudo mv ./${repoName}-private ./engine-private`);
78
+ shellCd(`${buildBasePath}/engine`);
79
+ shellExec(`npm install`);
80
+ const itcScripts = fs.readdir('./engine-private/itc-scripts');
81
+ for (const itcScript of itcScripts)
82
+ if (itcScript.match(deployId)) shellExec(`node ./engine-private/itc-scripts/${itcScript}`);
83
+ }
84
+ switch (deployId) {
85
+ case 'dd-lampp':
86
+ {
87
+ const lamppPublicPath = '/xampp/htdocs/online';
88
+ shellExec(`sudo mkdir -p ${lamppPublicPath}`);
89
+ }
90
+ break;
91
+
92
+ default:
93
+ {
94
+ {
95
+ const originPath = `./src/db/mongo/MongooseDB.js`;
96
+ fs.writeFileSync(
97
+ originPath,
98
+ fs.readFileSync(originPath, 'utf8').replaceAll(
99
+ `connect: async (host, name) => {`,
100
+ `connect: async (host, name) => {
101
+ host = 'mongodb://mongodb-0.mongodb-service:27017';
102
+ `,
103
+ ),
104
+ 'utf8',
105
+ );
106
+ }
107
+
108
+ {
109
+ const originPath = `./src/server/valkey.js`;
110
+ fs.writeFileSync(
111
+ originPath,
112
+ fs.readFileSync(originPath, 'utf8').replaceAll(
113
+ ` // port: 6379,
114
+ // host: 'service-valkey.default.svc.cluster.local',`,
115
+ ` port: 6379,
116
+ host: 'service-valkey.default.svc.cluster.local',`,
117
+ ),
118
+ 'utf8',
119
+ );
120
+ }
121
+ }
122
+ break;
123
+ }
124
+ shellExec(`node bin/deploy conf ${deployId} ${env}`);
125
+ shellExec(`node bin/deploy build-full-client ${deployId}`);
126
+ if (options.run === true) {
127
+ const runCmd = env === 'production' ? 'run prod-img' : 'run dev-img';
128
+ if (fs.existsSync(`./engine-private/replica`)) {
129
+ const replicas = await fs.readdir(`./engine-private/replica`);
130
+ for (const replica of replicas) {
131
+ shellExec(`node bin/deploy conf ${replica} ${env}`);
132
+ shellExec(`npm ${runCmd} ${replica} deploy`, { async: true });
133
+ fs.writeFileSync(`./tmp/await-deploy`, '', 'utf8');
134
+ const monitor = async () => {
135
+ await timer(1000);
136
+ if (fs.existsSync(`./tmp/await-deploy`)) return await monitor();
137
+ };
138
+ await monitor();
139
+ }
140
+ shellExec(`node bin/deploy conf ${deployId} ${env}`);
141
+ }
142
+ shellExec(`npm ${runCmd} ${deployId} deploy`);
143
+ }
144
+ },
145
+ },
146
+ };
147
+ }
148
+ export default UnderpostImage;
@@ -0,0 +1,125 @@
1
+ import { commitData } from '../client/components/core/CommonJs.js';
2
+ import dotenv from 'dotenv';
3
+ import { pbcopy, shellExec } from '../server/process.js';
4
+ import { actionInitLog, loggerFactory } from '../server/logger.js';
5
+ import fs from 'fs-extra';
6
+ import { getNpmRootPath } from '../server/conf.js';
7
+ import { listenPortController, listenServerFactory } from '../server/network.js';
8
+
9
+ dotenv.config();
10
+
11
+ const logger = loggerFactory(import.meta);
12
+
13
+ class UnderpostRepository {
14
+ static API = {
15
+ clone(gitUri = 'underpostnet/pwa-microservices-template', options = { bare: false }) {
16
+ const repoName = gitUri.split('/').pop();
17
+ if (fs.existsSync(`./${repoName}`)) fs.removeSync(`./${repoName}`);
18
+ shellExec(
19
+ `git clone ${options?.bare === true ? ` --bare ` : ''}https://${
20
+ process.env.GITHUB_TOKEN ? `${process.env.GITHUB_TOKEN}@` : ''
21
+ }github.com/${gitUri}.git`,
22
+ {
23
+ disableLog: true,
24
+ },
25
+ );
26
+ },
27
+ pull(repoPath = './', gitUri = 'underpostnet/pwa-microservices-template') {
28
+ shellExec(
29
+ `cd ${repoPath} && git pull https://${
30
+ process.env.GITHUB_TOKEN ? `${process.env.GITHUB_TOKEN}@` : ''
31
+ }github.com/${gitUri}.git`,
32
+ {
33
+ disableLog: true,
34
+ },
35
+ );
36
+ },
37
+ commit(
38
+ repoPath = './',
39
+ commitType = 'feat',
40
+ subModule = '',
41
+ message = '',
42
+ options = {
43
+ copy: false,
44
+ info: false,
45
+ empty: false,
46
+ },
47
+ ) {
48
+ if (commitType === 'reset') {
49
+ shellExec(`cd ${repoPath} && git reset --soft HEAD~${isNaN(parseInt(subModule)) ? 1 : parseInt(subModule)}`);
50
+ return;
51
+ }
52
+ if (options.info) return logger.info('', commitData);
53
+ const _message = `${commitType}${subModule ? `(${subModule})` : ''}${process.argv.includes('!') ? '!' : ''}: ${
54
+ commitData[commitType].emoji
55
+ } ${message ? message : commitData[commitType].description}`;
56
+ if (options.copy) return pbcopy(_message);
57
+ shellExec(`cd ${repoPath} && git commit ${options?.empty ? `--allow-empty ` : ''}-m "${_message}"`);
58
+ },
59
+
60
+ push(repoPath = './', gitUri = 'underpostnet/pwa-microservices-template', options = { f: false }) {
61
+ shellExec(
62
+ `cd ${repoPath} && git push https://${process.env.GITHUB_TOKEN}@github.com/${gitUri}.git${
63
+ options?.f === true ? ' --force' : ''
64
+ }`,
65
+ {
66
+ disableLog: true,
67
+ },
68
+ );
69
+ logger.info(
70
+ 'commit url',
71
+ `http://github.com/${gitUri}/commit/${shellExec(`cd ${repoPath} && git rev-parse --verify HEAD`, {
72
+ stdout: true,
73
+ }).trim()}`,
74
+ );
75
+ },
76
+
77
+ new(repositoryName) {
78
+ return new Promise(async (resolve, reject) => {
79
+ try {
80
+ await logger.setUpInfo();
81
+ if (repositoryName === 'service') return resolve(await listenPortController(listenServerFactory(), ':'));
82
+ else actionInitLog();
83
+ const exeRootPath = `${getNpmRootPath()}/underpost`;
84
+ const destFolder = `./${repositoryName}`;
85
+ logger.info('Note: This process may take several minutes to complete');
86
+ logger.info('build app', { destFolder });
87
+ if (fs.existsSync(destFolder)) fs.removeSync(destFolder);
88
+ fs.mkdirSync(destFolder, { recursive: true });
89
+ fs.copySync(exeRootPath, destFolder);
90
+ fs.writeFileSync(`${destFolder}/.gitignore`, fs.readFileSync(`${exeRootPath}/.dockerignore`, 'utf8'), 'utf8');
91
+ shellExec(`cd ${destFolder} && git init && git add . && git commit -m "Base template implementation"`);
92
+ shellExec(`cd ${destFolder} && npm run build`);
93
+ shellExec(`cd ${destFolder} && npm run dev`);
94
+ return resolve();
95
+ } catch (error) {
96
+ logger.error(error, error.stack);
97
+ return reject(error.message);
98
+ }
99
+ });
100
+ },
101
+
102
+ getDeleteFiles(path = '.') {
103
+ const commandUntrack = `cd ${path} && git ls-files --deleted`;
104
+ const diffUntrackOutput = shellExec(commandUntrack, { stdout: true, silent: true });
105
+ return diffUntrackOutput.toString().split('\n').filter(Boolean);
106
+ },
107
+
108
+ getChangedFiles(path = '.', extension = '', head = false) {
109
+ const extensionFilter = extension ? `-- '***.${extension}'` : '';
110
+ const command = `cd ${path} && git diff ${head ? 'HEAD^ HEAD ' : ''}--name-only ${extensionFilter}`;
111
+ const commandUntrack = `cd ${path} && git ls-files --others --exclude-standard`;
112
+ const diffOutput = shellExec(command, { stdout: true, silent: true });
113
+ const diffUntrackOutput = shellExec(commandUntrack, { stdout: true, silent: true });
114
+ const deleteFiles = UnderpostRepository.API.getDeleteFiles(path);
115
+ return diffOutput
116
+ .toString()
117
+ .split('\n')
118
+ .filter(Boolean)
119
+ .concat(diffUntrackOutput.toString().split('\n').filter(Boolean))
120
+ .filter((f) => !deleteFiles.includes(f));
121
+ },
122
+ };
123
+ }
124
+
125
+ export default UnderpostRepository;
@@ -0,0 +1,53 @@
1
+ import { getNpmRootPath } from '../server/conf.js';
2
+ import { loggerFactory } from '../server/logger.js';
3
+ import { shellExec } from '../server/process.js';
4
+ import fs from 'fs-extra';
5
+ import UnderpostDeploy from './deploy.js';
6
+
7
+ const logger = loggerFactory(import.meta);
8
+
9
+ class UnderpostScript {
10
+ static API = {
11
+ set(key, value) {
12
+ const npmRoot = `${getNpmRootPath()}/underpost`;
13
+ const packageJson = JSON.parse(fs.readFileSync(`${npmRoot}/package.json`, 'utf8'));
14
+ packageJson.scripts[key] = value;
15
+ fs.writeFileSync(`${npmRoot}/package.json`, JSON.stringify(packageJson, null, 4));
16
+ },
17
+ run(key, value, options) {
18
+ const npmRoot = `${getNpmRootPath()}/underpost`;
19
+ const packageJson = JSON.parse(fs.readFileSync(`${npmRoot}/package.json`, 'utf8'));
20
+ if (options.itc === true) {
21
+ value = packageJson.scripts[key];
22
+ const podScriptPath = `${options.itcPath && typeof options.itcPath === 'string' ? options.itcPath : '/'}${value
23
+ .split('/')
24
+ .pop()}`;
25
+ const nameSpace = options.ns && typeof options.ns === 'string' ? options.ns : 'default';
26
+ const podMatch = options.podName && typeof options.podName === 'string' ? options.podName : key;
27
+
28
+ if (fs.existsSync(`${value}`)) {
29
+ for (const pod of UnderpostDeploy.API.get(podMatch)) {
30
+ shellExec(`sudo kubectl cp ${value} ${nameSpace}/${pod.NAME}:${podScriptPath}`);
31
+ const cmd = `node ${podScriptPath}`;
32
+ shellExec(`sudo kubectl exec -i ${pod.NAME} -- sh -c "${cmd}"`);
33
+ }
34
+ } else {
35
+ for (const pod of UnderpostDeploy.API.get(podMatch)) {
36
+ shellExec(`sudo kubectl exec -i ${pod.NAME} -- sh -c "${value}"`);
37
+ }
38
+ }
39
+
40
+ return;
41
+ }
42
+ shellExec(`cd ${npmRoot} && npm run ${key}`);
43
+ },
44
+ get(key) {
45
+ const npmRoot = `${getNpmRootPath()}/underpost`;
46
+ const packageJson = JSON.parse(fs.readFileSync(`${npmRoot}/package.json`, 'utf8'));
47
+ logger.info('[get] ' + key, packageJson.scripts[key]);
48
+ return packageJson.scripts[key];
49
+ },
50
+ };
51
+ }
52
+
53
+ export default UnderpostScript;
@@ -0,0 +1,37 @@
1
+ import dotenv from 'dotenv';
2
+ import { shellExec } from '../server/process.js';
3
+ import fs from 'fs-extra';
4
+ import UnderpostRootEnv from './env.js';
5
+
6
+ class UnderpostSecret {
7
+ static API = {
8
+ docker: {
9
+ init() {
10
+ shellExec(`docker swarm init`);
11
+ },
12
+ createFromEnvFile(envPath) {
13
+ const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
14
+ for (const key of Object.keys(envObj)) {
15
+ UnderpostSecret.API.docker.set(key, envObj[key]);
16
+ }
17
+ },
18
+ set(key, value) {
19
+ shellExec(`docker secret rm ${key}`);
20
+ shellExec(`echo "${value}" | docker secret create ${key} -`);
21
+ },
22
+ list() {
23
+ shellExec(`docker secret ls`);
24
+ },
25
+ },
26
+ underpost: {
27
+ createFromEnvFile(envPath) {
28
+ const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
29
+ for (const key of Object.keys(envObj)) {
30
+ UnderpostRootEnv.API.set(key, envObj[key]);
31
+ }
32
+ },
33
+ },
34
+ };
35
+ }
36
+
37
+ export default UnderpostSecret;
@@ -0,0 +1,118 @@
1
+ import { timer } from '../client/components/core/CommonJs.js';
2
+ import { MariaDB } from '../db/mariadb/MariaDB.js';
3
+ import { getNpmRootPath } from '../server/conf.js';
4
+ import { actionInitLog, loggerFactory, setUpInfo } from '../server/logger.js';
5
+ import { pbcopy, shellExec } from '../server/process.js';
6
+ import UnderpostDeploy from './deploy.js';
7
+
8
+ const logger = loggerFactory(import.meta);
9
+
10
+ class UnderpostTest {
11
+ static API = {
12
+ /**
13
+ * Logs information about the current process environment to the console.
14
+ *
15
+ * This function is used to log details about
16
+ * the execution context, such as command-line arguments,
17
+ * environment variables, the process's administrative privileges,
18
+ * and the maximum available heap space size.
19
+ *
20
+ * @static
21
+ * @method setUpInfo
22
+ * @returns {Promise<void>}
23
+ * @memberof Underpost
24
+ */
25
+ async setUpInfo() {
26
+ return await setUpInfo(logger);
27
+ },
28
+ run() {
29
+ actionInitLog();
30
+ shellExec(`cd ${getNpmRootPath()}/underpost && npm run test`);
31
+ },
32
+ async callback(deployList = '', options = { itc: false, sh: false, logs: false }) {
33
+ if (
34
+ options.podName &&
35
+ typeof options.podName === 'string' &&
36
+ options.podStatus &&
37
+ typeof options.podStatus === 'string'
38
+ )
39
+ return await UnderpostTest.API.statusMonitor(options.podName, options.podStatus, options.kindType);
40
+
41
+ if (options.sh === true || options.logs === true) {
42
+ const [pod] = UnderpostDeploy.API.get(deployList);
43
+ if (pod) {
44
+ if (options.sh) return pbcopy(`sudo kubectl exec -it ${pod.NAME} -- sh`);
45
+ if (options.logs) return shellExec(`sudo kubectl logs -f ${pod.NAME}`);
46
+ }
47
+ return logger.warn(`Couldn't find pods in deployment`, deployList);
48
+ }
49
+ if (deployList) {
50
+ for (const _deployId of deployList.split(',')) {
51
+ const deployId = _deployId.trim();
52
+ if (!deployId) continue;
53
+ if (options.itc === true)
54
+ switch (deployId) {
55
+ case 'dd-lampp':
56
+ {
57
+ const { MARIADB_HOST, MARIADB_USER, MARIADB_PASSWORD, DD_LAMPP_TEST_DB_0 } = process.env;
58
+
59
+ await MariaDB.query({
60
+ host: MARIADB_HOST,
61
+ user: MARIADB_USER,
62
+ password: MARIADB_PASSWORD,
63
+ query: `SHOW TABLES FROM ${DD_LAMPP_TEST_DB_0}`,
64
+ });
65
+ }
66
+ break;
67
+
68
+ default:
69
+ {
70
+ shellExec('npm run test');
71
+ }
72
+
73
+ break;
74
+ }
75
+ else {
76
+ const pods = UnderpostDeploy.API.get(deployId);
77
+ if (pods.length > 0)
78
+ for (const deployData of pods) {
79
+ const { NAME } = deployData;
80
+ shellExec(
81
+ `sudo kubectl exec -i ${NAME} -- sh -c "cd /home/dd/engine && underpost test ${deployId} --inside-container"`,
82
+ );
83
+ }
84
+ else logger.warn(`Couldn't find pods in deployment`, { deployId });
85
+ }
86
+ }
87
+ } else return UnderpostTest.API.run();
88
+ },
89
+ statusMonitor(podName, status = 'Running', kindType = '', deltaMs = 1000, maxAttempts = 60 * 5) {
90
+ if (!(kindType && typeof kindType === 'string')) kindType = 'pods';
91
+ return new Promise(async (resolve) => {
92
+ let index = 0;
93
+ logger.info(`Loading instance`, { podName, status, kindType, deltaMs, maxAttempts });
94
+ const _monitor = async () => {
95
+ await timer(deltaMs);
96
+ const pods = UnderpostDeploy.API.get(podName, kindType);
97
+ const result = pods.find((p) => p.STATUS === status);
98
+ logger.info(
99
+ `Testing pod ${podName}... ${result ? 1 : 0}/1 - elapsed time ${deltaMs * (index + 1)}s - attempt ${
100
+ index + 1
101
+ }/${maxAttempts}`,
102
+ pods[0] ? pods[0].STATUS : 'Not found kind object',
103
+ );
104
+ if (result) return resolve(true);
105
+ index++;
106
+ if (index === maxAttempts) {
107
+ logger.error(`Failed to test pod ${podName} within ${maxAttempts} attempts`);
108
+ return resolve(false);
109
+ }
110
+ return _monitor();
111
+ };
112
+ await _monitor();
113
+ });
114
+ },
115
+ };
116
+ }
117
+
118
+ export default UnderpostTest;