ezpm2gui 1.0.0 → 1.1.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 (40) hide show
  1. package/README.md +202 -14
  2. package/bin/ezpm2gui.js +40 -44
  3. package/bin/ezpm2gui.ts +51 -0
  4. package/bin/generate-ecosystem.js +24 -22
  5. package/bin/generate-ecosystem.ts +56 -0
  6. package/dist/server/index.js +40 -20
  7. package/dist/server/routes/clusterManagement.d.ts +3 -0
  8. package/dist/server/routes/clusterManagement.js +152 -0
  9. package/dist/server/routes/deployApplication.d.ts +3 -0
  10. package/dist/server/routes/deployApplication.js +163 -0
  11. package/dist/server/routes/logStreaming.d.ts +5 -0
  12. package/dist/server/routes/logStreaming.js +102 -0
  13. package/dist/server/routes/modules.d.ts +3 -0
  14. package/dist/server/routes/modules.js +106 -0
  15. package/dist/server/routes/processConfig.d.ts +3 -0
  16. package/dist/server/routes/processConfig.js +118 -0
  17. package/dist/server/utils/pm2-connection.d.ts +16 -0
  18. package/dist/server/utils/pm2-connection.js +141 -0
  19. package/package.json +19 -5
  20. package/src/client/build/asset-manifest.json +6 -6
  21. package/src/client/build/index.html +1 -1
  22. package/src/client/build/static/css/main.672b8f26.css +2 -0
  23. package/src/client/build/static/css/main.672b8f26.css.map +1 -0
  24. package/src/client/build/static/js/main.1d7f99ff.js +156 -0
  25. package/src/client/build/static/js/{main.dde30e92.js.LICENSE.txt → main.1d7f99ff.js.LICENSE.txt} +19 -0
  26. package/src/client/build/static/js/main.1d7f99ff.js.map +1 -0
  27. package/ .npmignore +0 -39
  28. package/install.bat +0 -22
  29. package/install.sh +0 -23
  30. package/src/client/build/static/css/main.c1cbda3a.css +0 -2
  31. package/src/client/build/static/css/main.c1cbda3a.css.map +0 -1
  32. package/src/client/build/static/js/main.dde30e92.js +0 -3
  33. package/src/client/build/static/js/main.dde30e92.js.map +0 -1
  34. package/src/client/package-lock.json +0 -16192
  35. package/src/client/package.json +0 -39
  36. package/src/client/public/index.html +0 -20
  37. package/src/client/public/manifest.json +0 -25
  38. package/src/index.ts +0 -24
  39. package/src/server/index.ts +0 -240
  40. package/tsconfig.json +0 -18
@@ -0,0 +1,152 @@
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 pm2_1 = __importDefault(require("pm2"));
8
+ const router = (0, express_1.Router)();
9
+ // Get cluster information for a specific process
10
+ router.get('/:id', (req, res) => {
11
+ const { id } = req.params;
12
+ pm2_1.default.connect((err) => {
13
+ if (err) {
14
+ console.error(err);
15
+ res.status(500).json({ error: 'Failed to connect to PM2' });
16
+ return;
17
+ }
18
+ pm2_1.default.describe(id, (err, processDesc) => {
19
+ pm2_1.default.disconnect();
20
+ if (err || !processDesc || processDesc.length === 0) {
21
+ res.status(404).json({ error: 'Process not found' });
22
+ return;
23
+ }
24
+ const process = processDesc[0];
25
+ const clusterInfo = {
26
+ pm_id: process.pm_id,
27
+ name: process.name,
28
+ instances: process.pm2_env.instances,
29
+ exec_mode: process.pm2_env.exec_mode,
30
+ isCluster: process.pm2_env.exec_mode === 'cluster_mode'
31
+ };
32
+ res.json(clusterInfo);
33
+ });
34
+ });
35
+ });
36
+ // Scale process (change number of instances)
37
+ router.post('/:id/scale', (req, res) => {
38
+ const { id } = req.params;
39
+ const { instances } = req.body;
40
+ if (!instances || isNaN(parseInt(instances))) {
41
+ res.status(400).json({ error: 'Valid number of instances is required' });
42
+ return;
43
+ }
44
+ pm2_1.default.connect((err) => {
45
+ if (err) {
46
+ console.error(err);
47
+ res.status(500).json({ error: 'Failed to connect to PM2' });
48
+ return;
49
+ }
50
+ // Using PM2 API to scale a process
51
+ // TypeScript doesn't recognize 'scale' method, so we're using it with a type assertion
52
+ pm2_1.default.scale(id, parseInt(instances), (err) => {
53
+ pm2_1.default.disconnect();
54
+ if (err) {
55
+ console.error(err);
56
+ res.status(500).json({ error: 'Failed to scale process' });
57
+ return;
58
+ }
59
+ res.json({ success: true, message: `Process ${id} scaled to ${instances} instances` });
60
+ });
61
+ });
62
+ });
63
+ // Reload instances (zero-downtime reload)
64
+ router.post('/:id/reload', (req, res) => {
65
+ const { id } = req.params;
66
+ pm2_1.default.connect((err) => {
67
+ if (err) {
68
+ console.error(err);
69
+ res.status(500).json({ error: 'Failed to connect to PM2' });
70
+ return;
71
+ }
72
+ pm2_1.default.reload(id, (err) => {
73
+ pm2_1.default.disconnect();
74
+ if (err) {
75
+ console.error(err);
76
+ res.status(500).json({ error: 'Failed to reload process' });
77
+ return;
78
+ }
79
+ res.json({ success: true, message: `Process ${id} reloaded with zero-downtime` });
80
+ });
81
+ });
82
+ });
83
+ // Change execution mode between 'fork' and 'cluster'
84
+ router.post('/:id/exec-mode', (req, res) => {
85
+ const { id } = req.params;
86
+ const { mode } = req.body;
87
+ if (!mode || (mode !== 'fork' && mode !== 'cluster')) {
88
+ res.status(400).json({ error: 'Valid execution mode (fork or cluster) is required' });
89
+ return;
90
+ }
91
+ pm2_1.default.connect((err) => {
92
+ if (err) {
93
+ console.error(err);
94
+ res.status(500).json({ error: 'Failed to connect to PM2' });
95
+ return;
96
+ }
97
+ // First get the current process information to preserve settings
98
+ pm2_1.default.describe(id, (err, processDesc) => {
99
+ if (err || !processDesc || processDesc.length === 0) {
100
+ pm2_1.default.disconnect();
101
+ res.status(404).json({ error: 'Process not found' });
102
+ return;
103
+ }
104
+ const process = processDesc[0];
105
+ const pm2Env = process.pm2_env;
106
+ // Update the execution mode
107
+ const updateOptions = {
108
+ script: pm2Env.pm_exec_path,
109
+ name: process.name,
110
+ instances: pm2Env.instances || 1,
111
+ exec_mode: mode === 'cluster' ? 'cluster_mode' : 'fork_mode',
112
+ // Preserve other settings
113
+ cwd: pm2Env.pm_cwd,
114
+ watch: pm2Env.watch || false,
115
+ ignore_watch: pm2Env.ignore_watch || [],
116
+ env: pm2Env.env || {}
117
+ };
118
+ // Stop the existing process
119
+ pm2_1.default.stop(id, (stopErr) => {
120
+ if (stopErr) {
121
+ pm2_1.default.disconnect();
122
+ console.error(stopErr);
123
+ res.status(500).json({ error: 'Failed to stop process for exec mode change' });
124
+ return;
125
+ }
126
+ // Delete the existing process
127
+ pm2_1.default.del(id, (delErr) => {
128
+ if (delErr) {
129
+ pm2_1.default.disconnect();
130
+ console.error(delErr);
131
+ res.status(500).json({ error: 'Failed to delete process for exec mode change' });
132
+ return;
133
+ }
134
+ // Start with new settings
135
+ pm2_1.default.start(updateOptions, (startErr) => {
136
+ pm2_1.default.disconnect();
137
+ if (startErr) {
138
+ console.error(startErr);
139
+ res.status(500).json({ error: 'Failed to restart process with new exec mode' });
140
+ return;
141
+ }
142
+ res.json({
143
+ success: true,
144
+ message: `Process ${process.name} execution mode changed to ${mode}`
145
+ });
146
+ });
147
+ });
148
+ });
149
+ });
150
+ });
151
+ });
152
+ exports.default = router;
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ declare const router: Router;
3
+ export default router;
@@ -0,0 +1,163 @@
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 pm2_1 = __importDefault(require("pm2"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const router = (0, express_1.Router)();
11
+ // Deploy a new application
12
+ router.post('/', (req, res) => {
13
+ const { name, script, cwd, instances, exec_mode, autorestart, watch, max_memory_restart, env } = req.body;
14
+ // Validate required fields
15
+ if (!name || !script) {
16
+ return res.status(400).json({ error: 'Name and script path are required' });
17
+ }
18
+ // Validate script path exists
19
+ if (!fs_1.default.existsSync(script)) {
20
+ return res.status(400).json({ error: `Script file not found: ${script}` });
21
+ }
22
+ // Create deployment configuration
23
+ const appConfig = {
24
+ name,
25
+ script,
26
+ cwd: cwd || path_1.default.dirname(script),
27
+ instances: parseInt(instances) || 1,
28
+ exec_mode: exec_mode || 'fork',
29
+ autorestart: autorestart !== undefined ? autorestart : true,
30
+ watch: watch || false,
31
+ max_memory_restart: max_memory_restart || '150M',
32
+ env: env || {}
33
+ };
34
+ pm2_1.default.connect((err) => {
35
+ if (err) {
36
+ console.error(err);
37
+ return res.status(500).json({ error: 'Failed to connect to PM2' });
38
+ }
39
+ pm2_1.default.start(appConfig, (err) => {
40
+ pm2_1.default.disconnect();
41
+ if (err) {
42
+ console.error('PM2 start error:', err);
43
+ return res.status(500).json({
44
+ error: `Failed to deploy application: ${err.message || 'Unknown error'}`
45
+ });
46
+ }
47
+ res.json({
48
+ success: true,
49
+ message: `Application ${name} deployed successfully`
50
+ });
51
+ });
52
+ });
53
+ });
54
+ // Generate ecosystem.config.js file
55
+ router.post('/generate-ecosystem', (req, res) => {
56
+ pm2_1.default.connect(async (err) => {
57
+ if (err) {
58
+ console.error(err);
59
+ return res.status(500).json({ error: 'Failed to connect to PM2' });
60
+ }
61
+ try {
62
+ // Get all processes
63
+ const listPromise = new Promise((resolve, reject) => {
64
+ pm2_1.default.list((err, processList) => {
65
+ if (err) {
66
+ reject(err);
67
+ return;
68
+ }
69
+ resolve(processList);
70
+ });
71
+ });
72
+ const processList = await listPromise;
73
+ // Filter processes if needed
74
+ let filteredProcesses = processList;
75
+ if (req.body.includeAllProcesses === false) {
76
+ filteredProcesses = processList.filter(proc => proc.pm2_env.status === 'online');
77
+ }
78
+ // Create ecosystem config
79
+ const apps = filteredProcesses.map(proc => {
80
+ const pm2Env = proc.pm2_env;
81
+ return {
82
+ name: proc.name,
83
+ script: pm2Env.pm_exec_path,
84
+ cwd: pm2Env.pm_cwd,
85
+ instances: pm2Env.instances || 1,
86
+ exec_mode: pm2Env.exec_mode === 'cluster_mode' ? 'cluster' : 'fork',
87
+ autorestart: pm2Env.autorestart,
88
+ watch: pm2Env.watch,
89
+ ignore_watch: pm2Env.ignore_watch || [],
90
+ max_memory_restart: pm2Env.max_memory_restart || '150M',
91
+ env: pm2Env.env || {}
92
+ };
93
+ });
94
+ const ecosystemConfig = `module.exports = {
95
+ apps: ${JSON.stringify(apps, null, 2)}
96
+ };`;
97
+ // Create the file (either at specified path or default location)
98
+ const filePath = req.body.path || path_1.default.join(process.cwd(), 'ecosystem.config.js');
99
+ fs_1.default.writeFileSync(filePath, ecosystemConfig);
100
+ pm2_1.default.disconnect();
101
+ res.json({
102
+ success: true,
103
+ message: 'Ecosystem file generated successfully',
104
+ path: filePath
105
+ });
106
+ }
107
+ catch (error) {
108
+ pm2_1.default.disconnect();
109
+ res.status(500).json({ error: error.message || 'Failed to generate ecosystem file' });
110
+ }
111
+ });
112
+ });
113
+ // Preview ecosystem.config.js content
114
+ router.get('/generate-ecosystem-preview', (req, res) => {
115
+ pm2_1.default.connect(async (err) => {
116
+ if (err) {
117
+ console.error(err);
118
+ return res.status(500).json({ error: 'Failed to connect to PM2' });
119
+ }
120
+ try {
121
+ // Get all processes
122
+ const listPromise = new Promise((resolve, reject) => {
123
+ pm2_1.default.list((err, processList) => {
124
+ if (err) {
125
+ reject(err);
126
+ return;
127
+ }
128
+ resolve(processList);
129
+ });
130
+ });
131
+ const processList = await listPromise;
132
+ // Create ecosystem config
133
+ const apps = processList.map(proc => {
134
+ const pm2Env = proc.pm2_env;
135
+ return {
136
+ name: proc.name,
137
+ script: pm2Env.pm_exec_path,
138
+ cwd: pm2Env.pm_cwd,
139
+ instances: pm2Env.instances || 1,
140
+ exec_mode: pm2Env.exec_mode === 'cluster_mode' ? 'cluster' : 'fork',
141
+ autorestart: pm2Env.autorestart,
142
+ watch: pm2Env.watch,
143
+ ignore_watch: pm2Env.ignore_watch || [],
144
+ max_memory_restart: pm2Env.max_memory_restart || '150M',
145
+ env: pm2Env.env || {}
146
+ };
147
+ });
148
+ const ecosystemConfig = `module.exports = {
149
+ apps: ${JSON.stringify(apps, null, 2)}
150
+ };`;
151
+ pm2_1.default.disconnect();
152
+ res.json({
153
+ success: true,
154
+ content: ecosystemConfig
155
+ });
156
+ }
157
+ catch (error) {
158
+ pm2_1.default.disconnect();
159
+ res.status(500).json({ error: error.message || 'Failed to generate ecosystem preview' });
160
+ }
161
+ });
162
+ });
163
+ exports.default = router;
@@ -0,0 +1,5 @@
1
+ import { Router } from 'express';
2
+ declare const router: Router;
3
+ declare const setupLogStreaming: (io: any) => void;
4
+ export { setupLogStreaming };
5
+ export default router;
@@ -0,0 +1,102 @@
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
+ exports.setupLogStreaming = void 0;
7
+ const express_1 = require("express");
8
+ const pm2_1 = __importDefault(require("pm2"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const child_process_1 = require("child_process");
11
+ const router = (0, express_1.Router)();
12
+ // This variable will hold references to active log streams
13
+ const activeStreams = {};
14
+ // Get active stream (or create a new one)
15
+ const getLogStream = (io, processId, logType) => {
16
+ const streamKey = `${processId}-${logType}`;
17
+ // If stream already exists, return it
18
+ if (activeStreams[streamKey]) {
19
+ return activeStreams[streamKey];
20
+ }
21
+ return new Promise((resolve, reject) => {
22
+ pm2_1.default.describe(processId, (err, processDesc) => {
23
+ var _a, _b;
24
+ if (err || !processDesc || processDesc.length === 0) {
25
+ reject(new Error('Process not found'));
26
+ return;
27
+ }
28
+ const logPath = (_b = (_a = processDesc[0]) === null || _a === void 0 ? void 0 : _a.pm2_env) === null || _b === void 0 ? void 0 : _b[`pm_${logType}_log_path`];
29
+ if (!logPath || !fs_1.default.existsSync(logPath)) {
30
+ reject(new Error(`Log file not found: ${logPath}`));
31
+ return;
32
+ }
33
+ // Create a tail process to stream the log file
34
+ const tail = (0, child_process_1.spawn)('tail', ['-f', logPath]);
35
+ // Setup event handlers
36
+ tail.stdout.on('data', (data) => {
37
+ const lines = data.toString().split('\n').filter((line) => line.trim() !== '');
38
+ lines.forEach((line) => {
39
+ // Emit log line to all connected clients
40
+ io.emit('log-line', {
41
+ processId,
42
+ logType,
43
+ line
44
+ });
45
+ });
46
+ });
47
+ tail.stderr.on('data', (data) => {
48
+ console.error(`Tail error: ${data}`);
49
+ });
50
+ tail.on('close', (code) => {
51
+ console.log(`Tail process exited with code ${code}`);
52
+ delete activeStreams[streamKey];
53
+ });
54
+ // Store the tail process
55
+ activeStreams[streamKey] = tail;
56
+ resolve(tail);
57
+ });
58
+ });
59
+ };
60
+ // Setup socket.io handlers for log streaming
61
+ const setupLogStreaming = (io) => {
62
+ io.on('connection', (socket) => {
63
+ console.log('Client connected for log streaming');
64
+ // Subscribe to log stream
65
+ socket.on('subscribe-logs', async ({ processId, logType }) => {
66
+ try {
67
+ const streamKey = `${processId}-${logType}`;
68
+ // Add socket to a room for this specific log stream
69
+ socket.join(streamKey);
70
+ console.log(`Client subscribed to logs: ${streamKey}`);
71
+ // Get or create the log stream
72
+ await getLogStream(io, processId, logType);
73
+ }
74
+ catch (error) {
75
+ console.error('Error subscribing to logs:', error);
76
+ }
77
+ });
78
+ // Unsubscribe from log stream
79
+ socket.on('unsubscribe-logs', ({ processId, logType }) => {
80
+ const streamKey = `${processId}-${logType}`;
81
+ // Remove socket from the room
82
+ socket.leave(streamKey);
83
+ console.log(`Client unsubscribed from logs: ${streamKey}`);
84
+ // If no more clients in this room, stop the stream
85
+ const room = io.sockets.adapter.rooms.get(streamKey);
86
+ if (!room || room.size === 0) {
87
+ const stream = activeStreams[streamKey];
88
+ if (stream) {
89
+ console.log(`Stopping log stream: ${streamKey}`);
90
+ stream.kill();
91
+ delete activeStreams[streamKey];
92
+ }
93
+ }
94
+ });
95
+ // Cleanup on disconnect
96
+ socket.on('disconnect', () => {
97
+ console.log('Client disconnected from log streaming');
98
+ });
99
+ });
100
+ };
101
+ exports.setupLogStreaming = setupLogStreaming;
102
+ exports.default = router;
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ declare const router: Router;
3
+ export default router;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const child_process_1 = require("child_process");
5
+ const router = (0, express_1.Router)();
6
+ // List installed modules
7
+ router.get('/', (req, res) => {
8
+ const pm2Command = (0, child_process_1.spawn)('pm2', ['module:list']);
9
+ let output = '';
10
+ let errorOutput = '';
11
+ pm2Command.stdout.on('data', (data) => {
12
+ output += data.toString();
13
+ });
14
+ pm2Command.stderr.on('data', (data) => {
15
+ errorOutput += data.toString();
16
+ });
17
+ pm2Command.on('close', (code) => {
18
+ if (code !== 0) {
19
+ return res.status(500).json({
20
+ error: 'Failed to list PM2 modules',
21
+ details: errorOutput
22
+ });
23
+ }
24
+ // Parse the output to get module information
25
+ try {
26
+ const moduleLines = output.split('\n').filter(line => line.includes('│') && !line.includes('Module') && line.trim() !== '');
27
+ const modules = moduleLines.map(line => {
28
+ const parts = line.split('│').map(part => part.trim()).filter(Boolean);
29
+ if (parts.length >= 3) {
30
+ return {
31
+ name: parts[0],
32
+ version: parts[1],
33
+ status: parts[2]
34
+ };
35
+ }
36
+ return null;
37
+ }).filter(Boolean);
38
+ res.json(modules);
39
+ }
40
+ catch (error) {
41
+ res.status(500).json({
42
+ error: 'Failed to parse PM2 modules',
43
+ details: error
44
+ });
45
+ }
46
+ });
47
+ });
48
+ // Install a module
49
+ router.post('/install', (req, res) => {
50
+ const { moduleName } = req.body;
51
+ if (!moduleName) {
52
+ return res.status(400).json({ error: 'Module name is required' });
53
+ }
54
+ const pm2Command = (0, child_process_1.spawn)('pm2', ['install', moduleName]);
55
+ let output = '';
56
+ let errorOutput = '';
57
+ pm2Command.stdout.on('data', (data) => {
58
+ output += data.toString();
59
+ });
60
+ pm2Command.stderr.on('data', (data) => {
61
+ errorOutput += data.toString();
62
+ });
63
+ pm2Command.on('close', (code) => {
64
+ if (code !== 0) {
65
+ return res.status(500).json({
66
+ error: `Failed to install module: ${moduleName}`,
67
+ details: errorOutput
68
+ });
69
+ }
70
+ res.json({
71
+ success: true,
72
+ message: `Successfully installed module: ${moduleName}`,
73
+ details: output
74
+ });
75
+ });
76
+ });
77
+ // Uninstall a module
78
+ router.delete('/:moduleName', (req, res) => {
79
+ const { moduleName } = req.params;
80
+ if (!moduleName) {
81
+ return res.status(400).json({ error: 'Module name is required' });
82
+ }
83
+ const pm2Command = (0, child_process_1.spawn)('pm2', ['uninstall', moduleName]);
84
+ let output = '';
85
+ let errorOutput = '';
86
+ pm2Command.stdout.on('data', (data) => {
87
+ output += data.toString();
88
+ });
89
+ pm2Command.stderr.on('data', (data) => {
90
+ errorOutput += data.toString();
91
+ });
92
+ pm2Command.on('close', (code) => {
93
+ if (code !== 0) {
94
+ return res.status(500).json({
95
+ error: `Failed to uninstall module: ${moduleName}`,
96
+ details: errorOutput
97
+ });
98
+ }
99
+ res.json({
100
+ success: true,
101
+ message: `Successfully uninstalled module: ${moduleName}`,
102
+ details: output
103
+ });
104
+ });
105
+ });
106
+ exports.default = router;
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ declare const router: Router;
3
+ export default router;
@@ -0,0 +1,118 @@
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 pm2_1 = __importDefault(require("pm2"));
8
+ const router = (0, express_1.Router)();
9
+ // Get current configuration for a process
10
+ router.get('/:id', (req, res) => {
11
+ const { id } = req.params;
12
+ pm2_1.default.connect((err) => {
13
+ if (err) {
14
+ console.error(err);
15
+ res.status(500).json({ error: 'Failed to connect to PM2' });
16
+ return;
17
+ }
18
+ pm2_1.default.describe(id, (err, processDesc) => {
19
+ pm2_1.default.disconnect();
20
+ if (err || !processDesc || processDesc.length === 0) {
21
+ res.status(404).json({ error: 'Process not found' });
22
+ return;
23
+ }
24
+ const process = processDesc[0];
25
+ const pm2Env = process.pm2_env;
26
+ // Extract configuration from PM2 environment
27
+ const config = {
28
+ name: process.name,
29
+ script: pm2Env.pm_exec_path,
30
+ cwd: pm2Env.pm_cwd,
31
+ interpreter: pm2Env.exec_interpreter,
32
+ instances: pm2Env.instances || 1,
33
+ exec_mode: pm2Env.exec_mode === 'cluster_mode' ? 'cluster' : 'fork',
34
+ autorestart: pm2Env.autorestart,
35
+ watch: pm2Env.watch,
36
+ ignore_watch: pm2Env.ignore_watch || [],
37
+ env: pm2Env.env || {},
38
+ max_memory_restart: pm2Env.max_memory_restart || '150M'
39
+ };
40
+ res.json(config);
41
+ });
42
+ });
43
+ });
44
+ // Update configuration for a process
45
+ router.post('/:id', (req, res) => {
46
+ const { id } = req.params;
47
+ const config = req.body;
48
+ if (!config) {
49
+ res.status(400).json({ error: 'Configuration data is required' });
50
+ return;
51
+ }
52
+ pm2_1.default.connect(async (err) => {
53
+ if (err) {
54
+ console.error(err);
55
+ res.status(500).json({ error: 'Failed to connect to PM2' });
56
+ return;
57
+ }
58
+ try {
59
+ // First get the current process to find its ecosystem file if any
60
+ const describePromise = new Promise((resolve, reject) => {
61
+ pm2_1.default.describe(id, (err, processDesc) => {
62
+ if (err || !processDesc || processDesc.length === 0) {
63
+ reject(new Error('Process not found'));
64
+ return;
65
+ }
66
+ resolve(processDesc[0]);
67
+ });
68
+ });
69
+ const process = await describePromise;
70
+ // Update process via PM2 API
71
+ const updatePromise = new Promise((resolve, reject) => {
72
+ // Stop the process first
73
+ pm2_1.default.stop(id, (err) => {
74
+ if (err) {
75
+ reject(new Error(`Failed to stop process: ${err.message}`));
76
+ return;
77
+ }
78
+ // Delete the process
79
+ // TypeScript doesn't recognize 'del' method, so we're using it with a type assertion
80
+ pm2_1.default.del(id, (err) => {
81
+ if (err) {
82
+ reject(new Error(`Failed to delete process: ${err.message}`));
83
+ return;
84
+ }
85
+ // Start with new configuration
86
+ pm2_1.default.start({
87
+ name: config.name,
88
+ script: config.script,
89
+ cwd: config.cwd,
90
+ interpreter: config.interpreter,
91
+ instances: config.instances,
92
+ exec_mode: config.exec_mode,
93
+ autorestart: config.autorestart,
94
+ watch: config.watch,
95
+ ignore_watch: config.ignore_watch,
96
+ env: config.env,
97
+ max_memory_restart: config.max_memory_restart
98
+ }, (err) => {
99
+ if (err) {
100
+ reject(new Error(`Failed to start process with new config: ${err.message}`));
101
+ return;
102
+ }
103
+ resolve(true);
104
+ });
105
+ });
106
+ });
107
+ });
108
+ await updatePromise;
109
+ pm2_1.default.disconnect();
110
+ res.json({ success: true, message: 'Configuration updated successfully' });
111
+ }
112
+ catch (error) {
113
+ pm2_1.default.disconnect();
114
+ res.status(500).json({ error: error.message || 'Failed to update configuration' });
115
+ }
116
+ });
117
+ });
118
+ exports.default = router;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Connect to PM2 if not already connected
3
+ * Returns a promise that resolves when the connection is established
4
+ */
5
+ export declare const connectToPM2: () => Promise<void>;
6
+ /**
7
+ * Safely disconnect from PM2
8
+ * Use this when shutting down the server
9
+ */
10
+ export declare const disconnectFromPM2: () => Promise<void>;
11
+ /**
12
+ * Execute a PM2 command with automatic connection handling
13
+ * This will connect to PM2 if needed, run the command,
14
+ * and properly handle the result
15
+ */
16
+ export declare const executePM2Command: <T>(command: (callback: (err: Error | null, result?: T) => void) => void) => Promise<T>;