ezpm2gui 1.3.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +295 -294
  2. package/bin/ezpm2gui.js +8 -8
  3. package/bin/ezpm2gui.ts +51 -51
  4. package/bin/generate-ecosystem.js +35 -35
  5. package/bin/generate-ecosystem.ts +56 -56
  6. package/dist/index.js +1 -1
  7. package/dist/server/config/project-configs.json +236 -236
  8. package/dist/server/index.js +256 -83
  9. package/dist/server/routes/deployApplication.js +6 -5
  10. package/dist/server/routes/logStreaming.js +20 -13
  11. package/dist/server/routes/modules.js +89 -69
  12. package/dist/server/routes/remoteConnections.js +279 -40
  13. package/dist/server/routes/updates.d.ts +3 -0
  14. package/dist/server/routes/updates.js +135 -0
  15. package/dist/server/utils/encryption.js +0 -12
  16. package/dist/server/utils/pm2-connection.d.ts +1 -1
  17. package/dist/server/utils/pm2-connection.js +1 -3
  18. package/dist/server/utils/remote-connection.d.ts +36 -3
  19. package/dist/server/utils/remote-connection.js +307 -79
  20. package/package.json +73 -69
  21. package/scripts/postinstall.js +36 -36
  22. package/src/client/build/asset-manifest.json +6 -6
  23. package/src/client/build/favicon.ico +2 -2
  24. package/src/client/build/index.html +1 -1
  25. package/src/client/build/logo192.svg +7 -7
  26. package/src/client/build/logo512.svg +7 -7
  27. package/src/client/build/manifest.json +24 -24
  28. package/src/client/build/static/css/main.2d095544.css +5 -0
  29. package/src/client/build/static/css/main.2d095544.css.map +1 -0
  30. package/src/client/build/static/js/main.17e17668.js +3 -0
  31. package/src/client/build/static/js/main.17e17668.js.map +1 -0
  32. package/dist/server/config/cron-jobs.json +0 -18
  33. package/dist/server/config/cron-scripts/6d8d5e1d-2bc8-463f-82a6-6c294f2b9dbe.sh +0 -2
  34. package/dist/server/config/remote-connections.json +0 -22
  35. package/dist/server/logs/deployment.log +0 -12
  36. package/dist/server/utils/dialog.d.ts +0 -1
  37. package/dist/server/utils/dialog.js +0 -16
  38. package/dist/server/utils/upload.d.ts +0 -3
  39. package/dist/server/utils/upload.js +0 -39
  40. package/src/client/build/static/css/main.d46bc75c.css +0 -5
  41. package/src/client/build/static/css/main.d46bc75c.css.map +0 -1
  42. package/src/client/build/static/js/main.b0e1c9b1.js +0 -3
  43. package/src/client/build/static/js/main.b0e1c9b1.js.map +0 -1
  44. /package/src/client/build/static/js/{main.b0e1c9b1.js.LICENSE.txt → main.17e17668.js.LICENSE.txt} +0 -0
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const express_1 = require("express");
7
+ const child_process_1 = require("child_process");
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const https_1 = __importDefault(require("https"));
11
+ // @group Utilities : Read the current installed version from package.json
12
+ const getCurrentVersion = () => {
13
+ try {
14
+ // Try the package root (works when run from source or dist)
15
+ const candidates = [
16
+ path_1.default.resolve(__dirname, '../../../package.json'),
17
+ path_1.default.resolve(__dirname, '../../package.json'),
18
+ path_1.default.resolve(process.cwd(), 'package.json'),
19
+ ];
20
+ for (const p of candidates) {
21
+ if (fs_1.default.existsSync(p)) {
22
+ const pkg = JSON.parse(fs_1.default.readFileSync(p, 'utf8'));
23
+ if (pkg.name === 'ezpm2gui')
24
+ return pkg.version;
25
+ }
26
+ }
27
+ }
28
+ catch {
29
+ // fall through
30
+ }
31
+ return '0.0.0';
32
+ };
33
+ // @group Utilities : Fetch latest version info from npm registry (no external deps)
34
+ const fetchNpmLatest = () => new Promise((resolve, reject) => {
35
+ const req = https_1.default.get('https://registry.npmjs.org/ezpm2gui/latest', { headers: { Accept: 'application/json' } }, (res) => {
36
+ let data = '';
37
+ res.on('data', (chunk) => { data += chunk; });
38
+ res.on('end', () => {
39
+ var _a;
40
+ try {
41
+ const json = JSON.parse(data);
42
+ resolve({
43
+ version: json.version,
44
+ description: json.description,
45
+ publishedAt: (_a = json.time) === null || _a === void 0 ? void 0 : _a.modified,
46
+ });
47
+ }
48
+ catch {
49
+ reject(new Error('Failed to parse npm registry response'));
50
+ }
51
+ });
52
+ });
53
+ req.on('error', reject);
54
+ req.setTimeout(8000, () => { req.destroy(); reject(new Error('npm registry request timed out')); });
55
+ });
56
+ // @group Utilities : Compare semver strings — returns true if b is newer than a
57
+ const isNewer = (current, latest) => {
58
+ const parse = (v) => v.replace(/^v/, '').split('.').map(Number);
59
+ const [cMaj, cMin, cPat] = parse(current);
60
+ const [lMaj, lMin, lPat] = parse(latest);
61
+ if (lMaj !== cMaj)
62
+ return lMaj > cMaj;
63
+ if (lMin !== cMin)
64
+ return lMin > cMin;
65
+ return lPat > cPat;
66
+ };
67
+ const router = (0, express_1.Router)();
68
+ // @group CheckUpdate : GET /api/update/check — returns current vs latest npm version
69
+ router.get('/check', async (_req, res) => {
70
+ try {
71
+ const currentVersion = getCurrentVersion();
72
+ const { version: latestVersion, publishedAt } = await fetchNpmLatest();
73
+ const updateAvailable = isNewer(currentVersion, latestVersion);
74
+ const result = {
75
+ currentVersion,
76
+ latestVersion,
77
+ updateAvailable,
78
+ publishedAt,
79
+ };
80
+ res.json({ success: true, data: result });
81
+ }
82
+ catch (err) {
83
+ console.error('Update check failed:', err);
84
+ res.status(503).json({ success: false, error: err.message || 'Failed to reach npm registry' });
85
+ }
86
+ });
87
+ // @group InstallUpdate : POST /api/update/install — installs ezpm2gui@latest globally
88
+ // Streams progress lines as newline-delimited JSON (ndjson) so the client can read incrementally.
89
+ router.post('/install', (req, res) => {
90
+ res.setHeader('Content-Type', 'application/x-ndjson');
91
+ res.setHeader('Transfer-Encoding', 'chunked');
92
+ res.setHeader('Cache-Control', 'no-cache');
93
+ res.flushHeaders();
94
+ const send = (type, message) => {
95
+ res.write(JSON.stringify({ type, message }) + '\n');
96
+ };
97
+ send('log', 'Starting update — running npm install -g ezpm2gui@latest...');
98
+ // Use `npm` with execFile for safety — no shell injection possible
99
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
100
+ const child = (0, child_process_1.spawn)(npmCmd, ['install', '-g', 'ezpm2gui@latest'], {
101
+ stdio: ['ignore', 'pipe', 'pipe'],
102
+ shell: false,
103
+ });
104
+ child.stdout.on('data', (chunk) => {
105
+ chunk.toString().split('\n').filter(Boolean).forEach((line) => send('log', line));
106
+ });
107
+ child.stderr.on('data', (chunk) => {
108
+ // npm writes progress to stderr — treat as log unless it's an actual error keyword
109
+ const text = chunk.toString();
110
+ const isError = /^npm (ERR|error)/i.test(text.trim());
111
+ text.split('\n').filter(Boolean).forEach((line) => send(isError ? 'error' : 'log', line));
112
+ });
113
+ child.on('close', (code) => {
114
+ if (code === 0) {
115
+ send('done', 'Update installed successfully. Reload the page to use the new frontend. Restart the server to apply backend changes.');
116
+ }
117
+ else {
118
+ send('fail', `npm exited with code ${code}. Update may have failed.`);
119
+ }
120
+ res.end();
121
+ });
122
+ child.on('error', (err) => {
123
+ send('fail', `Failed to run npm: ${err.message}`);
124
+ res.end();
125
+ });
126
+ });
127
+ // @group RestartServer : POST /api/update/restart — graceful server restart (relies on process manager to respawn)
128
+ router.post('/restart', (_req, res) => {
129
+ res.json({ success: true, message: 'Server will restart in 1 second.' });
130
+ setTimeout(() => {
131
+ console.log('[ezpm2gui] Graceful restart triggered via API.');
132
+ process.exit(0);
133
+ }, 1000);
134
+ });
135
+ exports.default = router;
@@ -44,29 +44,17 @@ function encrypt(text) {
44
44
  function decrypt(encryptedText) {
45
45
  if (!encryptedText)
46
46
  return '';
47
- // Special handling for manually entered passwords in the config file
48
- if (encryptedText === '11342b0ca35d70c17955d874e5d4b0a26547521f705a6f74320b5d7bfeb56369') {
49
- console.log('Using hardcoded credential for specific password hash');
50
- return 'test1234';
51
- }
52
47
  try {
53
- console.log(`Attempting to decrypt: ${encryptedText.substring(0, 10)}...`);
54
48
  // Ensure key and IV are the correct length
55
49
  const key = crypto_1.default.createHash('sha256').update(String(ENCRYPTION_KEY)).digest('base64').slice(0, 32);
56
50
  const iv = crypto_1.default.createHash('sha256').update(String(ENCRYPTION_IV)).digest('base64').slice(0, 16);
57
51
  const decipher = crypto_1.default.createDecipheriv(ALGORITHM, key, iv);
58
52
  let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
59
53
  decrypted += decipher.final('utf8');
60
- console.log('Decryption successful');
61
54
  return decrypted;
62
55
  }
63
56
  catch (error) {
64
57
  console.error('Decryption error:', error);
65
- // For development only - return a value even if decryption fails
66
- if (encryptedText && encryptedText.length > 10) {
67
- console.log('Returning fallback credential for development');
68
- return 'test1234';
69
- }
70
58
  return '';
71
59
  }
72
60
  }
@@ -13,4 +13,4 @@ export declare const disconnectFromPM2: () => Promise<void>;
13
13
  * This will connect to PM2 if needed, run the command,
14
14
  * and properly handle the result
15
15
  */
16
- export declare const executePM2Command: <T>(command: (callback: (err: Error | null, result?: T) => void) => void) => Promise<T>;
16
+ export declare const executePM2Command: <T = any>(command: (callback: (err: Error | null, result?: T) => void) => void) => Promise<T>;
@@ -103,6 +103,7 @@ exports.disconnectFromPM2 = disconnectFromPM2;
103
103
  * This will connect to PM2 if needed, run the command,
104
104
  * and properly handle the result
105
105
  */
106
+ // @group ConnectionPool : Execute a PM2 command using the shared pooled connection
106
107
  const executePM2Command = async (command) => {
107
108
  try {
108
109
  await (0, exports.connectToPM2)();
@@ -111,9 +112,6 @@ const executePM2Command = async (command) => {
111
112
  if (err) {
112
113
  reject(err);
113
114
  }
114
- else if (result === undefined) {
115
- reject(new Error('PM2 command returned undefined result'));
116
- }
117
115
  else {
118
116
  resolve(result);
119
117
  }
@@ -54,16 +54,49 @@ export declare class RemoteConnection extends EventEmitter {
54
54
  * @param forceSudo Whether to force using sudo for this specific command
55
55
  */
56
56
  executeCommand(command: string, forceSudo?: boolean): Promise<CommandResult>;
57
+ /**
58
+ * Stream a remote file directly into an HTTP response without buffering.
59
+ *
60
+ * Strategy (tried in order):
61
+ * 1. SFTP createReadStream — best; handles large files, uses file-transfer protocol
62
+ * 2. exec `cat <file>` — current SSH user, direct pipe to response
63
+ * 3. exec `sudo -S cat` — password-based sudo (when password auth is configured)
64
+ * 4. exec `sudo cat` — NOPASSWD sudo (key-based auth where sudo needs no password)
65
+ */
66
+ private static readonly SHELL_UNSAFE;
67
+ private static validateRemotePath;
68
+ streamFileToResponse(remotePath: string, res: import('express').Response, fileName: string): Promise<void>;
69
+ /**
70
+ * Stream a remote .gz file to an HTTP response, decompressing it on the fly.
71
+ * Uses SFTP + local zlib.createGunzip() pipeline — no server-side buffering.
72
+ * Falls back to exec `zcat` if SFTP is unavailable.
73
+ */
74
+ streamGzFileToResponse(remotePath: string, res: import('express').Response, fileName: string): Promise<void>;
57
75
  /**
58
76
  * Check if PM2 is installed on the remote server
59
77
  * Uses multiple detection methods for better reliability
60
78
  */
61
79
  checkPM2Installation(): Promise<boolean>;
62
80
  /**
63
- * Execute a PM2 command with proper PATH handling
64
- * Tries different methods to find and execute PM2
81
+ * Ordered list of command builders tried when resolving the pm2 binary.
82
+ * The index of the first successful builder is cached so that subsequent
83
+ * calls (including streaming commands) reuse the same invocation.
84
+ */
85
+ private static readonly PM2_BUILDERS;
86
+ /** Index into PM2_BUILDERS of the invocation that actually works on this host. -1 = not yet resolved. */
87
+ private resolvedBuilderIndex;
88
+ /**
89
+ * Execute a PM2 command with proper PATH handling.
90
+ * The first successful invocation is cached so later calls (and streaming
91
+ * commands) reuse the same shell/binary path without re-probing.
92
+ */
93
+ executePM2Command(pm2Args: string): Promise<CommandResult>;
94
+ /**
95
+ * Build a pm2 command string using the already-resolved invocation pattern.
96
+ * Falls back to bash -li -c if the resolver has not yet run.
97
+ * Use this when you need the raw command string for a streaming shell exec.
65
98
  */
66
- private executePM2Command;
99
+ buildPM2StreamCommand(pm2Args: string): string;
67
100
  /**
68
101
  * Get PM2 processes from the remote server
69
102
  */