underpost 3.1.3 → 3.2.2

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 (92) hide show
  1. package/.env.example +0 -2
  2. package/.github/workflows/ghpkg.ci.yml +4 -4
  3. package/.github/workflows/npmpkg.ci.yml +28 -11
  4. package/.github/workflows/publish.ci.yml +6 -0
  5. package/.github/workflows/pwa-microservices-template-page.cd.yml +4 -5
  6. package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
  7. package/.github/workflows/release.cd.yml +13 -8
  8. package/CHANGELOG.md +396 -1
  9. package/CLI-HELP.md +53 -6
  10. package/Dockerfile +4 -2
  11. package/README.md +3 -2
  12. package/bin/build.js +18 -12
  13. package/bin/deploy.js +177 -124
  14. package/bin/file.js +3 -0
  15. package/conf.js +3 -2
  16. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -2
  17. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +5 -2
  18. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  19. package/manifests/deployment/dd-test-development/deployment.yaml +88 -74
  20. package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
  21. package/manifests/deployment/playwright/deployment.yaml +1 -1
  22. package/nodemon.json +1 -1
  23. package/package.json +22 -15
  24. package/scripts/rhel-grpc-setup.sh +56 -0
  25. package/src/api/file/file.ref.json +18 -0
  26. package/src/api/user/user.service.js +8 -7
  27. package/src/cli/cluster.js +7 -7
  28. package/src/cli/db.js +726 -825
  29. package/src/cli/deploy.js +151 -93
  30. package/src/cli/env.js +19 -0
  31. package/src/cli/fs.js +5 -2
  32. package/src/cli/index.js +45 -2
  33. package/src/cli/kubectl.js +211 -0
  34. package/src/cli/release.js +284 -0
  35. package/src/cli/repository.js +434 -75
  36. package/src/cli/run.js +189 -34
  37. package/src/cli/secrets.js +73 -0
  38. package/src/cli/test.js +3 -3
  39. package/src/client/Default.index.js +3 -4
  40. package/src/client/components/core/AppStore.js +69 -0
  41. package/src/client/components/core/CalendarCore.js +2 -2
  42. package/src/client/components/core/DropDown.js +137 -17
  43. package/src/client/components/core/Keyboard.js +2 -2
  44. package/src/client/components/core/LogIn.js +2 -2
  45. package/src/client/components/core/LogOut.js +2 -2
  46. package/src/client/components/core/Modal.js +0 -1
  47. package/src/client/components/core/Panel.js +0 -1
  48. package/src/client/components/core/PanelForm.js +19 -19
  49. package/src/client/components/core/SocketIo.js +82 -29
  50. package/src/client/components/core/SocketIoHandler.js +75 -0
  51. package/src/client/components/core/Stream.js +143 -95
  52. package/src/client/components/core/Webhook.js +40 -7
  53. package/src/client/components/default/AppStoreDefault.js +5 -0
  54. package/src/client/components/default/LogInDefault.js +3 -3
  55. package/src/client/components/default/LogOutDefault.js +2 -2
  56. package/src/client/components/default/MenuDefault.js +5 -5
  57. package/src/client/components/default/SocketIoDefault.js +3 -51
  58. package/src/client/services/core/core.service.js +20 -8
  59. package/src/client/services/user/user.management.js +2 -2
  60. package/src/index.js +24 -1
  61. package/src/runtime/express/Dockerfile +4 -0
  62. package/src/runtime/express/Express.js +18 -1
  63. package/src/runtime/lampp/Dockerfile +13 -2
  64. package/src/runtime/lampp/Lampp.js +27 -4
  65. package/src/runtime/wp/Dockerfile +68 -0
  66. package/src/runtime/wp/Wp.js +639 -0
  67. package/src/server/auth.js +24 -1
  68. package/src/server/backup.js +57 -23
  69. package/src/server/client-build-docs.js +9 -2
  70. package/src/server/client-build.js +31 -31
  71. package/src/server/client-formatted.js +109 -57
  72. package/src/server/cron.js +23 -18
  73. package/src/server/ipfs-client.js +24 -1
  74. package/src/server/peer.js +8 -0
  75. package/src/server/runtime.js +25 -1
  76. package/src/server/start.js +3 -2
  77. package/src/ws/IoInterface.js +1 -10
  78. package/src/ws/IoServer.js +14 -33
  79. package/src/ws/core/channels/core.ws.chat.js +65 -20
  80. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  81. package/src/ws/core/channels/core.ws.stream.js +90 -31
  82. package/src/ws/core/core.ws.connection.js +12 -33
  83. package/src/ws/core/core.ws.emit.js +10 -26
  84. package/src/ws/core/core.ws.server.js +25 -58
  85. package/src/ws/default/channels/default.ws.main.js +53 -12
  86. package/src/ws/default/default.ws.connection.js +26 -13
  87. package/src/ws/default/default.ws.server.js +30 -12
  88. package/src/client/components/default/ElementsDefault.js +0 -38
  89. package/src/ws/core/management/core.ws.chat.js +0 -8
  90. package/src/ws/core/management/core.ws.mailer.js +0 -16
  91. package/src/ws/core/management/core.ws.stream.js +0 -8
  92. package/src/ws/default/management/default.ws.main.js +0 -8
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Kubectl module providing low-level Kubernetes resource management primitives.
3
+ * Centralises pod querying, file transfer, and in-container execution operations
4
+ * that were previously scattered across db, deploy, and cluster modules.
5
+ * @module src/cli/kubectl.js
6
+ * @namespace UnderpostKubectl
7
+ */
8
+
9
+ import { loggerFactory } from '../server/logger.js';
10
+ import { shellExec } from '../server/process.js';
11
+ import Underpost from '../index.js';
12
+
13
+ const logger = loggerFactory(import.meta);
14
+
15
+ /**
16
+ * Redacts credentials from shell command strings before logging.
17
+ * Masks passwords in `-p<password>`, `--password=<password>`, and `-P <password>` patterns.
18
+ * @param {string} cmd - The raw command string.
19
+ * @returns {string} The command with credentials replaced by `***`.
20
+ * @memberof UnderpostKubectl
21
+ */
22
+ const sanitizeCommand = (cmd) => {
23
+ if (typeof cmd !== 'string') return cmd;
24
+ return cmd
25
+ .replace(/-p['"]?[^\s'"]+/g, '-p***')
26
+ .replace(/--password=['"]?[^\s'"]+/g, '--password=***')
27
+ .replace(/-P\s+['"]?[^\s'"]+/g, '-P ***');
28
+ };
29
+
30
+ /**
31
+ * @class UnderpostKubectl
32
+ * @description Kubernetes cluster resource management primitives.
33
+ * Provides a unified interface for kubectl operations: resource listing, in-pod
34
+ * command execution, file transfer, and pod discovery/filtering.
35
+ * All methods are stateless and safe to call from any other CLI module.
36
+ * @memberof UnderpostKubectl
37
+ */
38
+ class UnderpostKubectl {
39
+ static API = {
40
+ /**
41
+ * Lists Kubernetes resources matching `deployId`, parsed into plain objects.
42
+ * Equivalent to `kubectl get <kindType> -o wide`, filtered by name substring.
43
+ * @param {string} deployId - Substring to match against resource names. Empty string returns all.
44
+ * @param {string} [kindType='pods'] - Resource kind: pods, deployments, svc, nodes, …
45
+ * @param {string} [namespace=''] - Namespace to query; empty string → --all-namespaces.
46
+ * @returns {Array<object>} Parsed rows keyed by column header (NAME, STATUS, NODE, …).
47
+ * @memberof UnderpostKubectl
48
+ */
49
+ get(deployId, kindType = 'pods', namespace = '') {
50
+ const raw = shellExec(
51
+ `sudo kubectl get ${kindType}${namespace ? ` -n ${namespace}` : ` --all-namespaces`} -o wide`,
52
+ { stdout: true, disableLog: true, silent: true },
53
+ );
54
+
55
+ const heads = raw
56
+ .split(`\n`)[0]
57
+ .split(' ')
58
+ .filter((_r) => _r.trim());
59
+
60
+ const pods = raw
61
+ .split(`\n`)
62
+ .filter((r) => (deployId ? r.match(deployId) : r.trim() && !r.match('NAME')))
63
+ .map((r) => r.split(' ').filter((_r) => _r.trim()));
64
+
65
+ const result = [];
66
+ for (const row of pods) {
67
+ const pod = {};
68
+ let index = -1;
69
+ for (const head of heads) {
70
+ index++;
71
+ pod[head] = row[index];
72
+ }
73
+ result.push(pod);
74
+ }
75
+ return result;
76
+ },
77
+
78
+ /**
79
+ * Executes a kubectl command with credential-safe logging and error propagation.
80
+ * @param {string} command - Full kubectl command string.
81
+ * @param {object} [options={}] - Execution options.
82
+ * @param {string} [options.context=''] - Human-readable label for log messages.
83
+ * @returns {string} stdout output from the command.
84
+ * @throws {Error} Re-throws any execution error after logging.
85
+ * @memberof UnderpostKubectl
86
+ */
87
+ run(command, options = {}) {
88
+ const { context = '' } = options;
89
+ try {
90
+ logger.info(`Executing kubectl command`, { command: sanitizeCommand(command), context });
91
+ return shellExec(command, { stdout: true, disableLog: true });
92
+ } catch (error) {
93
+ logger.error(`kubectl command failed`, { command: sanitizeCommand(command), error: error.message, context });
94
+ throw error;
95
+ }
96
+ },
97
+
98
+ /**
99
+ * Runs a shell command inside a pod container via `kubectl exec`.
100
+ * @param {object} params
101
+ * @param {string} params.podName - Target pod name.
102
+ * @param {string} params.namespace - Pod namespace.
103
+ * @param {string} params.command - Shell command to run inside the container.
104
+ * @returns {string} stdout output from the in-pod command.
105
+ * @throws {Error} Re-throws any execution error after logging.
106
+ * @memberof UnderpostKubectl
107
+ */
108
+ exec({ podName, namespace, command }) {
109
+ try {
110
+ const kubectlCmd = `sudo kubectl exec -n ${namespace} -i ${podName} -- sh -c "${command}"`;
111
+ return Underpost.kubectl.run(kubectlCmd, { context: `exec in pod ${podName}` });
112
+ } catch (error) {
113
+ logger.error('Failed to execute command in pod', {
114
+ podName,
115
+ command: sanitizeCommand(command),
116
+ error: error.message,
117
+ });
118
+ throw error;
119
+ }
120
+ },
121
+
122
+ /**
123
+ * Copies a local file into a pod via `kubectl cp`.
124
+ * @param {object} params
125
+ * @param {string} params.sourcePath - Local source path.
126
+ * @param {string} params.podName - Target pod name.
127
+ * @param {string} params.namespace - Pod namespace.
128
+ * @param {string} params.destPath - Destination path inside the container.
129
+ * @returns {boolean} `true` on success, `false` on error.
130
+ * @memberof UnderpostKubectl
131
+ */
132
+ cpTo({ sourcePath, podName, namespace, destPath }) {
133
+ try {
134
+ const command = `sudo kubectl cp ${sourcePath} ${namespace}/${podName}:${destPath}`;
135
+ Underpost.kubectl.run(command, { context: `copy to pod ${podName}` });
136
+ return true;
137
+ } catch (error) {
138
+ logger.error('Failed to copy file to pod', { sourcePath, podName, destPath, error: error.message });
139
+ return false;
140
+ }
141
+ },
142
+
143
+ /**
144
+ * Copies a file from a pod to the local filesystem via `kubectl cp`.
145
+ * @param {object} params
146
+ * @param {string} params.podName - Source pod name.
147
+ * @param {string} params.namespace - Pod namespace.
148
+ * @param {string} params.sourcePath - Source path inside the container.
149
+ * @param {string} params.destPath - Local destination path.
150
+ * @returns {boolean} `true` on success, `false` on error.
151
+ * @memberof UnderpostKubectl
152
+ */
153
+ cpFrom({ podName, namespace, sourcePath, destPath }) {
154
+ try {
155
+ const command = `sudo kubectl cp ${namespace}/${podName}:${sourcePath} ${destPath}`;
156
+ Underpost.kubectl.run(command, { context: `copy from pod ${podName}` });
157
+ return true;
158
+ } catch (error) {
159
+ logger.error('Failed to copy file from pod', { podName, sourcePath, destPath, error: error.message });
160
+ return false;
161
+ }
162
+ },
163
+
164
+ /**
165
+ * Checks whether a file exists inside a pod container.
166
+ * @param {object} params
167
+ * @param {string} params.podName - Pod name.
168
+ * @param {string} params.path - Absolute path inside the container to test.
169
+ * @returns {boolean} `true` if the file exists.
170
+ * @memberof UnderpostKubectl
171
+ */
172
+ existsFile({ podName, path }) {
173
+ const result = shellExec(`kubectl exec ${podName} -- test -f ${path} && echo "true" || echo "false"`, {
174
+ stdout: true,
175
+ disableLog: true,
176
+ silent: true,
177
+ }).trim();
178
+ return result === 'true';
179
+ },
180
+
181
+ /**
182
+ * Returns a filtered list of pods from the cluster.
183
+ * Supports wildcard glob patterns on pod names and optional deployId substring filtering.
184
+ * @param {object} [criteria={}] - Filter criteria.
185
+ * @param {string} [criteria.deployId] - Substring to match against pod names (forwards to `get`).
186
+ * @param {string} [criteria.podNames] - Comma-separated glob patterns (supports `*`).
187
+ * @param {string} [criteria.namespace='default'] - Kubernetes namespace to query.
188
+ * @returns {Array<object>} Filtered pod rows from `get`.
189
+ * @memberof UnderpostKubectl
190
+ */
191
+ getFilteredPods(criteria = {}) {
192
+ const { podNames, namespace = 'default', deployId } = criteria;
193
+ try {
194
+ let pods = Underpost.kubectl.get(deployId || '', 'pods', namespace);
195
+ if (podNames) {
196
+ const patterns = podNames.split(',').map((p) => p.trim());
197
+ pods = pods.filter((pod) =>
198
+ patterns.some((pattern) => new RegExp('^' + pattern.replace(/\*/g, '.*') + '$').test(pod.NAME)),
199
+ );
200
+ }
201
+ logger.info(`Found ${pods.length} pod(s) matching criteria`, { criteria, podNames: pods.map((p) => p.NAME) });
202
+ return pods;
203
+ } catch (error) {
204
+ logger.error('Error filtering pods', { error: error.message, criteria });
205
+ return [];
206
+ }
207
+ },
208
+ };
209
+ }
210
+
211
+ export default UnderpostKubectl;
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Release orchestrator module for managing version builds and deployments of the Underpost CLI.
3
+ *
4
+ * Provides automated workflows for building new versions (bumping version numbers across
5
+ * all package files, manifests, and configurations) and deploying releases (committing,
6
+ * pushing, and syncing secrets to remote repositories).
7
+ *
8
+ * @module src/cli/release.js
9
+ * @namespace UnderpostRelease
10
+ */
11
+
12
+ import fs from 'fs-extra';
13
+ import dotenv from 'dotenv';
14
+ import { pbcopy, shellCd, shellExec } from '../server/process.js';
15
+ import { loggerFactory } from '../server/logger.js';
16
+ import { timer } from '../client/components/core/CommonJs.js';
17
+ import Underpost from '../index.js';
18
+
19
+ const logger = loggerFactory(import.meta);
20
+
21
+ /**
22
+ * Kills any Node.js dev-server or nodemon processes that may hold file locks
23
+ * (e.g. overwriting package.json). Skips VSCode internals and the current process.
24
+ */
25
+ function killDevServers() {
26
+ // shellExec(
27
+ // `kill -9 $(pgrep -f 'nodemon|node.*src/server|node.*dev' | grep -v '^${process.pid}$') 2>/dev/null || true`,
28
+ // );
29
+ shellExec(`node bin run kill 4001`);
30
+ shellExec(`node bin run kill 4002`);
31
+ shellExec(`node bin run kill 4003`);
32
+ shellExec(`node bin run kill 3000`);
33
+ }
34
+
35
+ /**
36
+ * @class UnderpostRelease
37
+ * @description Orchestrates version builds and release deployments for the Underpost CLI.
38
+ * This class provides static methods to automate the full release lifecycle:
39
+ * building a new version (testing, bumping versions, rebuilding manifests)
40
+ * and deploying a release (syncing secrets, committing, and pushing to remotes).
41
+ * @memberof UnderpostRelease
42
+ */
43
+ class UnderpostRelease {
44
+ static API = {
45
+ /**
46
+ * Builds a new version of the Underpost engine.
47
+ *
48
+ * Performs the full version build pipeline:
49
+ * 1. Loads production environment and pulls latest code.
50
+ * 2. Kills running dev servers on ports 4001-4003.
51
+ * 3. Builds and tests the pwa-microservices-template.
52
+ * 4. Bumps version in package.json, package-lock.json, and all conf package files.
53
+ * 5. Updates deployment YAML manifests and Docker image CI workflow with new version.
54
+ * 6. Updates src/index.js version string.
55
+ * 7. Rebuilds CLI docs, dependencies, client builds, deploy manifests, and default confs.
56
+ * 8. Syncs cron setup-start scripts and builds changelog.
57
+ *
58
+ * @method build
59
+ * @param {string} [newVersion] - The new version string to set. Defaults to current version if not provided.
60
+ * @param {object} [options] - Commander options object (unused, reserved for future flags).
61
+ * @memberof UnderpostRelease
62
+ */
63
+ async build(newVersion, options) {
64
+ dotenv.config({ path: `./engine-private/conf/dd-cron/.env.production`, override: true });
65
+ shellCd(`/home/dd/engine`);
66
+ killDevServers();
67
+ Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] });
68
+ shellExec(`node bin pull . ${process.env.GITHUB_USERNAME}/engine`);
69
+ shellExec(`npm run update:template`);
70
+ shellExec(`cd ../pwa-microservices-template && npm install && npm run build`);
71
+ console.log(fs.existsSync(`../pwa-microservices-template/engine-private/conf/dd-default`));
72
+ shellExec(`cd ../pwa-microservices-template && ENABLE_FILE_LOGS=true timeout 5s npm run dev`, {
73
+ async: true,
74
+ });
75
+ await timer(5500);
76
+ const templateRunnerResult = fs.readFileSync(`../pwa-microservices-template/logs/start.js/all.log`, 'utf8');
77
+ logger.info('Test template runner result');
78
+ console.log(templateRunnerResult);
79
+ if (!templateRunnerResult || templateRunnerResult.toLowerCase().match('error')) {
80
+ logger.error('Test template runner result failed');
81
+ return;
82
+ }
83
+ killDevServers();
84
+ shellCd(`/home/dd/engine`);
85
+ Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] });
86
+ const originPackageJson = JSON.parse(fs.readFileSync(`package.json`, 'utf8'));
87
+ if (!newVersion) newVersion = originPackageJson.version;
88
+ const { version } = originPackageJson;
89
+ originPackageJson.version = newVersion;
90
+ fs.writeFileSync(`package.json`, JSON.stringify(originPackageJson, null, 4), 'utf8');
91
+
92
+ const originPackageLockJson = JSON.parse(fs.readFileSync(`package-lock.json`, 'utf8'));
93
+ originPackageLockJson.version = newVersion;
94
+ originPackageLockJson.packages[''].version = newVersion;
95
+ fs.writeFileSync(`package-lock.json`, JSON.stringify(originPackageLockJson, null, 4), 'utf8');
96
+
97
+ if (fs.existsSync(`./engine-private/conf`)) {
98
+ const files = await fs.readdir(`./engine-private/conf`, { recursive: true });
99
+ for (const relativePath of files) {
100
+ const filePah = `./engine-private/conf/${relativePath.replaceAll(`\\`, '/')}`;
101
+ if (filePah.split('/').pop() === 'package.json') {
102
+ const originPackage = JSON.parse(fs.readFileSync(filePah, 'utf8'));
103
+ originPackage.version = newVersion;
104
+ fs.writeFileSync(filePah, JSON.stringify(originPackage, null, 4), 'utf8');
105
+ }
106
+ if (filePah.split('/').pop() === 'deployment.yaml') {
107
+ fs.writeFileSync(
108
+ filePah,
109
+ fs
110
+ .readFileSync(filePah, 'utf8')
111
+ .replaceAll(`v${version}`, `v${newVersion}`)
112
+ .replaceAll(`engine.version: ${version}`, `engine.version: ${newVersion}`),
113
+ 'utf8',
114
+ );
115
+ }
116
+ }
117
+ }
118
+
119
+ fs.writeFileSync(
120
+ `./manifests/deployment/dd-default-development/deployment.yaml`,
121
+ fs
122
+ .readFileSync(`./manifests/deployment/dd-default-development/deployment.yaml`, 'utf8')
123
+ .replaceAll(`underpost:v${version}`, `underpost:v${newVersion}`),
124
+ 'utf8',
125
+ );
126
+
127
+ if (fs.existsSync(`./.github/workflows/docker-image.ci.yml`))
128
+ fs.writeFileSync(
129
+ `./.github/workflows/docker-image.ci.yml`,
130
+ fs
131
+ .readFileSync(`./.github/workflows/docker-image.ci.yml`, 'utf8')
132
+ .replaceAll(`underpost-engine:v${version}`, `underpost-engine:v${newVersion}`),
133
+ 'utf8',
134
+ );
135
+
136
+ fs.writeFileSync(
137
+ `./src/index.js`,
138
+ fs.readFileSync(`./src/index.js`, 'utf8').replaceAll(`${version}`, `${newVersion}`),
139
+ 'utf8',
140
+ );
141
+ shellExec(`node bin/deploy cli-docs ${version} ${newVersion}`);
142
+ shellExec(`node bin/deploy update-dependencies`);
143
+ shellExec(`node bin/build dd`);
144
+ shellExec(`node bin deploy --build-manifest --sync --info-router --replicas 1 dd production`);
145
+ shellExec(`node bin deploy --build-manifest --sync --info-router --replicas 1 dd development`);
146
+ shellExec(`node bin/deploy build-default-confs`);
147
+ shellExec(`sudo rm -rf ./engine-private/conf/dd-default`);
148
+ shellExec(`node bin new --deploy-id dd-default`);
149
+ console.log(fs.existsSync(`./engine-private/conf/dd-default`));
150
+ shellExec(`sudo rm -rf ./engine-private/conf/dd-default`);
151
+ shellExec(`node bin cron --dev --setup-start`);
152
+ shellExec(`node bin cmt --changelog-build`);
153
+ },
154
+
155
+ /**
156
+ * Runs the local equivalent of an engine-*.ci.yml GitHub Actions workflow.
157
+ *
158
+ * Mirrors the CI pipeline locally:
159
+ * 1. Loads production environment (for GITHUB_TOKEN / GITHUB_USERNAME).
160
+ * 2. Clones pwa-microservices-template and engine-{suffix} (bare) into the parent dir (/home/dd).
161
+ * 3. Builds dd-{suffix} development from the engine directory.
162
+ * 4. Replaces .git in pwa-microservices-template with the bare-cloned git, then commits and pushes
163
+ * to the underpostnet/engine-{suffix} remote repository.
164
+ *
165
+ * @method ci
166
+ * @param {string} deployId - The deploy-id suffix (e.g., "cyberia", "core", "lampp", "test").
167
+ * Accepts "cyberia", "dd-cyberia", or "engine-cyberia" — the prefix is stripped automatically.
168
+ * @param {string} [message] - Optional commit message. Defaults to the last commit message of pwa-microservices-template.
169
+ * @param {object} [options] - Commander options object (unused, reserved for future flags).
170
+ * @memberof UnderpostRelease
171
+ */
172
+ async ci(deployId, message, options) {
173
+ dotenv.config({ path: `./engine-private/conf/dd-cron/.env.production`, override: true });
174
+ const suffix = deployId.replace(/^(dd-|engine-)/, '');
175
+ const repoName = `engine-${suffix}`;
176
+ const buildTarget = `dd-${suffix}`;
177
+ const githubOrg = process.env.GITHUB_USERNAME || 'underpostnet';
178
+ shellCd('/home/dd');
179
+ shellExec(`sudo rm -rf /home/dd/pwa-microservices-template`);
180
+ shellExec(`node engine/bin clone ${githubOrg}/pwa-microservices-template`);
181
+ // Use the message passed from the caller (engine repo changelog);
182
+ // fall back to the engine repo's last commit if not provided.
183
+ let commitMsg = message;
184
+ if (!commitMsg) {
185
+ shellCd('/home/dd/engine');
186
+ const rawMsg = shellExec(`node bin cmt --changelog 1 --changelog-no-hash`, {
187
+ stdout: true,
188
+ silent: true,
189
+ }).trim();
190
+ commitMsg = Underpost.repo.sanitizeChangelogMessage(rawMsg);
191
+ shellCd('/home/dd');
192
+ }
193
+ commitMsg = (commitMsg || '').trim() || `Update ${repoName} repository`;
194
+ logger.info(`CI push commit message: ${commitMsg}`);
195
+ shellExec(`node engine/bin clone --bare ${githubOrg}/${repoName}`);
196
+ shellCd('/home/dd/engine');
197
+ shellExec(`node bin/build ${buildTarget}`);
198
+ shellCd('/home/dd/pwa-microservices-template');
199
+ shellExec(`rm -rf ./.git`);
200
+ shellExec(`mv ../${repoName}.git ./.git`);
201
+ shellExec(`git config --local core.bare false`);
202
+ shellExec(`git reset`);
203
+ Underpost.repo.initLocalRepo({ path: '/home/dd/pwa-microservices-template' });
204
+ return {
205
+ triggerCmd: `cd /home/dd/pwa-microservices-template && git add . && git commit -m "${commitMsg}" && node ../engine/bin push . ${githubOrg}/${repoName}`,
206
+ };
207
+ },
208
+
209
+ /**
210
+ * Runs the pwa-microservices-template update and push flow locally.
211
+ *
212
+ * Always removes and re-clones pwa-microservices-template, then:
213
+ * 1. Runs update:template (node bin/file update-template) to sync engine sources.
214
+ * 2. Installs dependencies and builds the template.
215
+ * 3. Commits and pushes to the pwa-microservices-template remote repository.
216
+ *
217
+ * @method pwa
218
+ * @param {string} [message] - Optional commit message. Defaults to last commit message of pwa-microservices-template.
219
+ * @param {object} [options] - Commander options object (unused, reserved for future flags).
220
+ * @memberof UnderpostRelease
221
+ */
222
+ async pwa(message, options) {
223
+ dotenv.config({ path: `./engine-private/conf/dd-cron/.env.production`, override: true });
224
+ const githubOrg = process.env.GITHUB_USERNAME || 'underpostnet';
225
+ // Use the message passed from the caller (engine repo changelog);
226
+ // fall back to the engine repo's last commit if not provided.
227
+ let commitMsg = message;
228
+ if (!commitMsg) {
229
+ shellCd('/home/dd/engine');
230
+ const rawMsg = shellExec(`node bin cmt --changelog 1 --changelog-no-hash`, {
231
+ stdout: true,
232
+ silent: true,
233
+ }).trim();
234
+ commitMsg = Underpost.repo.sanitizeChangelogMessage(rawMsg);
235
+ }
236
+ commitMsg = (commitMsg || '').trim() || `Update pwa-microservices-template repository`;
237
+ shellCd('/home/dd');
238
+ shellExec(`sudo rm -rf /home/dd/pwa-microservices-template`);
239
+ shellExec(`node engine/bin clone ${githubOrg}/pwa-microservices-template`);
240
+ shellCd('/home/dd/engine');
241
+ shellExec(`npm run update:template`);
242
+ shellExec(`cd ../pwa-microservices-template && npm install && npm run build`);
243
+ shellCd('/home/dd/pwa-microservices-template');
244
+ shellExec(`git add .`);
245
+ // shellExec(`git commit -m "${commitMsg}"`);
246
+ return {
247
+ triggerCmd: `node bin push . ${githubOrg}/engine && cd /home/dd/pwa-microservices-template && git commit -m "${commitMsg}" && node ../engine/bin push . ${githubOrg}/pwa-microservices-template`,
248
+ };
249
+ },
250
+
251
+ /**
252
+ * Deploys a new version release to remote repositories.
253
+ *
254
+ * Performs the release deployment pipeline:
255
+ * 1. Loads production environment from dd-cron.
256
+ * 2. Syncs Underpost secrets from the production env file.
257
+ * 3. Builds the dd configuration.
258
+ * 4. Stages all changes in both engine and engine-private repositories.
259
+ * 5. Commits with a release message including the version tag.
260
+ * 6. Pushes both repositories to their respective GitHub remotes.
261
+ *
262
+ * @method deploy
263
+ * @param {string} [version] - The version string for the release commit message (e.g., "3.1.4").
264
+ * @param {object} [options] - Commander options object (unused, reserved for future flags).
265
+ * @memberof UnderpostRelease
266
+ */
267
+ async deploy(version, options) {
268
+ dotenv.config({ path: `./engine-private/conf/dd-cron/.env.production`, override: true });
269
+ killDevServers();
270
+ shellExec(
271
+ `node bin secret underpost --create-from-file /home/dd/engine/engine-private/conf/dd-cron/.env.production`,
272
+ );
273
+ shellExec(`node bin/build dd conf`);
274
+ shellExec(`git add . && cd ./engine-private && git add .`);
275
+ shellExec(`node bin cmt . ci package-pwa-microservices-template 'New release v:${version}'`);
276
+ shellExec(`node bin cmt ./engine-private ci package-pwa-microservices-template`);
277
+ shellExec(`node bin push . ${process.env.GITHUB_USERNAME}/engine`);
278
+ shellExec(`cd ./engine-private && node ../bin push . ${process.env.GITHUB_USERNAME}/engine-private`);
279
+ },
280
+ };
281
+ }
282
+
283
+ export { UnderpostRelease };
284
+ export default UnderpostRelease;