jm2 0.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.
- package/GNU-AGPL-3.0 +665 -0
- package/README.md +603 -0
- package/bin/jm2.js +24 -0
- package/package.json +70 -0
- package/src/cli/commands/add.js +206 -0
- package/src/cli/commands/config.js +212 -0
- package/src/cli/commands/edit.js +198 -0
- package/src/cli/commands/export.js +61 -0
- package/src/cli/commands/flush.js +132 -0
- package/src/cli/commands/history.js +179 -0
- package/src/cli/commands/import.js +180 -0
- package/src/cli/commands/list.js +174 -0
- package/src/cli/commands/logs.js +415 -0
- package/src/cli/commands/pause.js +97 -0
- package/src/cli/commands/remove.js +107 -0
- package/src/cli/commands/restart.js +68 -0
- package/src/cli/commands/resume.js +96 -0
- package/src/cli/commands/run.js +115 -0
- package/src/cli/commands/show.js +159 -0
- package/src/cli/commands/start.js +46 -0
- package/src/cli/commands/status.js +47 -0
- package/src/cli/commands/stop.js +48 -0
- package/src/cli/index.js +274 -0
- package/src/cli/utils/output.js +267 -0
- package/src/cli/utils/prompts.js +56 -0
- package/src/core/config.js +227 -0
- package/src/core/history-db.js +439 -0
- package/src/core/job.js +329 -0
- package/src/core/logger.js +382 -0
- package/src/core/storage.js +315 -0
- package/src/daemon/executor.js +409 -0
- package/src/daemon/index.js +873 -0
- package/src/daemon/scheduler.js +465 -0
- package/src/ipc/client.js +112 -0
- package/src/ipc/protocol.js +183 -0
- package/src/ipc/server.js +92 -0
- package/src/utils/cron.js +205 -0
- package/src/utils/datetime.js +237 -0
- package/src/utils/duration.js +226 -0
- package/src/utils/paths.js +164 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JM2 resume command
|
|
3
|
+
* Resumes one or more paused jobs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { send } from '../../ipc/client.js';
|
|
7
|
+
import { MessageType } from '../../ipc/protocol.js';
|
|
8
|
+
import { printSuccess, printError, printWarning } from '../utils/output.js';
|
|
9
|
+
import { isDaemonRunning } from '../../daemon/index.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Execute the resume command
|
|
13
|
+
* @param {string|string[]} jobRefs - Job ID(s) or name(s)
|
|
14
|
+
* @param {object} options - Command options
|
|
15
|
+
* @returns {Promise<number>} Exit code
|
|
16
|
+
*/
|
|
17
|
+
export async function resumeCommand(jobRefs, options = {}) {
|
|
18
|
+
// Check if daemon is running
|
|
19
|
+
if (!isDaemonRunning()) {
|
|
20
|
+
printError('Daemon is not running. Start it with: jm2 start');
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Normalize jobRefs to array
|
|
25
|
+
const refs = Array.isArray(jobRefs) ? jobRefs : [jobRefs];
|
|
26
|
+
|
|
27
|
+
if (refs.length === 0 || (refs.length === 1 && !refs[0])) {
|
|
28
|
+
printError('Job ID or name is required');
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let successCount = 0;
|
|
33
|
+
let failCount = 0;
|
|
34
|
+
|
|
35
|
+
for (const jobRef of refs) {
|
|
36
|
+
const result = await resumeSingleJob(jobRef);
|
|
37
|
+
if (result) {
|
|
38
|
+
successCount++;
|
|
39
|
+
} else {
|
|
40
|
+
failCount++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Summary
|
|
45
|
+
if (successCount > 0) {
|
|
46
|
+
printSuccess(`Resumed ${successCount} job(s)`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (failCount > 0) {
|
|
50
|
+
printError(`Failed to resume ${failCount} job(s)`);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resume a single job
|
|
59
|
+
* @param {string} jobRef - Job ID or name
|
|
60
|
+
* @returns {Promise<boolean>} True if successful
|
|
61
|
+
*/
|
|
62
|
+
async function resumeSingleJob(jobRef) {
|
|
63
|
+
try {
|
|
64
|
+
// Determine if jobRef is an ID (numeric) or name
|
|
65
|
+
const jobId = parseInt(jobRef, 10);
|
|
66
|
+
const message = isNaN(jobId)
|
|
67
|
+
? { type: MessageType.JOB_RESUME, jobName: jobRef }
|
|
68
|
+
: { type: MessageType.JOB_RESUME, jobId };
|
|
69
|
+
|
|
70
|
+
const response = await send(message);
|
|
71
|
+
|
|
72
|
+
if (response.type === MessageType.ERROR) {
|
|
73
|
+
printError(`${jobRef}: ${response.message}`);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (response.type === MessageType.JOB_RESUMED) {
|
|
78
|
+
if (response.job) {
|
|
79
|
+
const name = response.job.name || response.job.id;
|
|
80
|
+
printSuccess(`Resumed: ${name}`);
|
|
81
|
+
return true;
|
|
82
|
+
} else {
|
|
83
|
+
printWarning(`Job not found: ${jobRef}`);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
printError(`${jobRef}: Unexpected response from daemon`);
|
|
89
|
+
return false;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
printError(`${jobRef}: ${error.message}`);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default { resumeCommand };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JM2 run command
|
|
3
|
+
* Manually execute a job immediately
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { send } from '../../ipc/client.js';
|
|
7
|
+
import { MessageType } from '../../ipc/protocol.js';
|
|
8
|
+
import { printSuccess, printError, printInfo } from '../utils/output.js';
|
|
9
|
+
import { isDaemonRunning } from '../../daemon/index.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Execute the run command
|
|
13
|
+
* @param {string} jobRef - Job ID or name
|
|
14
|
+
* @param {object} options - Command options
|
|
15
|
+
* @returns {Promise<number>} Exit code
|
|
16
|
+
*/
|
|
17
|
+
export async function runCommand(jobRef, options = {}) {
|
|
18
|
+
// Check if daemon is running
|
|
19
|
+
if (!isDaemonRunning()) {
|
|
20
|
+
printError('Daemon is not running. Start it with: jm2 start');
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!jobRef || jobRef.trim() === '') {
|
|
25
|
+
printError('Job ID or name is required');
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Determine if jobRef is an ID (numeric) or name
|
|
31
|
+
const jobId = parseInt(jobRef, 10);
|
|
32
|
+
const message = isNaN(jobId)
|
|
33
|
+
? { type: MessageType.JOB_RUN, jobName: jobRef, wait: options.wait }
|
|
34
|
+
: { type: MessageType.JOB_RUN, jobId, wait: options.wait };
|
|
35
|
+
|
|
36
|
+
printInfo(`Running job: ${jobRef}...`);
|
|
37
|
+
|
|
38
|
+
const response = await send(message);
|
|
39
|
+
|
|
40
|
+
if (response.type === MessageType.ERROR) {
|
|
41
|
+
printError(response.message);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (response.type === MessageType.JOB_RUN_RESULT) {
|
|
46
|
+
const result = response.result;
|
|
47
|
+
|
|
48
|
+
if (result.error) {
|
|
49
|
+
printError(`Job execution failed: ${result.error}`);
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (options.wait) {
|
|
54
|
+
// Display execution results
|
|
55
|
+
if (result.status === 'success') {
|
|
56
|
+
printSuccess('Job completed successfully');
|
|
57
|
+
if (result.stdout) {
|
|
58
|
+
console.log('\n--- stdout ---');
|
|
59
|
+
console.log(result.stdout);
|
|
60
|
+
}
|
|
61
|
+
if (result.stderr) {
|
|
62
|
+
console.log('\n--- stderr ---');
|
|
63
|
+
console.log(result.stderr);
|
|
64
|
+
}
|
|
65
|
+
console.log(`\nExit code: ${result.exitCode || 0}`);
|
|
66
|
+
console.log(`Duration: ${formatDuration(result.duration || 0)}`);
|
|
67
|
+
} else if (result.status === 'timeout') {
|
|
68
|
+
printError('Job timed out');
|
|
69
|
+
return 1;
|
|
70
|
+
} else {
|
|
71
|
+
printError(`Job failed with status: ${result.status}`);
|
|
72
|
+
if (result.stdout) {
|
|
73
|
+
console.log('\n--- stdout ---');
|
|
74
|
+
console.log(result.stdout);
|
|
75
|
+
}
|
|
76
|
+
if (result.stderr) {
|
|
77
|
+
console.log('\n--- stderr ---');
|
|
78
|
+
console.log(result.stderr);
|
|
79
|
+
}
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
printSuccess(`Job queued for execution (ID: ${result.jobId || jobRef})`);
|
|
84
|
+
printInfo('Use --wait to wait for completion and see output');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
printError('Unexpected response from daemon');
|
|
91
|
+
return 1;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
printError(`Failed to run job: ${error.message}`);
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Format duration in milliseconds to human-readable string
|
|
100
|
+
* @param {number} ms - Duration in milliseconds
|
|
101
|
+
* @returns {string} Formatted duration
|
|
102
|
+
*/
|
|
103
|
+
function formatDuration(ms) {
|
|
104
|
+
if (ms < 1000) {
|
|
105
|
+
return `${ms}ms`;
|
|
106
|
+
}
|
|
107
|
+
if (ms < 60000) {
|
|
108
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
109
|
+
}
|
|
110
|
+
const minutes = Math.floor(ms / 60000);
|
|
111
|
+
const seconds = ((ms % 60000) / 1000).toFixed(1);
|
|
112
|
+
return `${minutes}m ${seconds}s`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default { runCommand };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JM2 show command
|
|
3
|
+
* Shows detailed information about a job
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { send } from '../../ipc/client.js';
|
|
7
|
+
import { MessageType } from '../../ipc/protocol.js';
|
|
8
|
+
import {
|
|
9
|
+
printSuccess,
|
|
10
|
+
printError,
|
|
11
|
+
printInfo,
|
|
12
|
+
printHeader,
|
|
13
|
+
colorizeStatus,
|
|
14
|
+
formatDate,
|
|
15
|
+
formatRelativeTime,
|
|
16
|
+
formatJobSchedule,
|
|
17
|
+
} from '../utils/output.js';
|
|
18
|
+
import { isDaemonRunning } from '../../daemon/index.js';
|
|
19
|
+
import { getJobLogFile } from '../../utils/paths.js';
|
|
20
|
+
import chalk from 'chalk';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Execute the show command
|
|
24
|
+
* @param {string} jobRef - Job ID or name
|
|
25
|
+
* @param {object} options - Command options
|
|
26
|
+
* @returns {Promise<number>} Exit code
|
|
27
|
+
*/
|
|
28
|
+
export async function showCommand(jobRef, options = {}) {
|
|
29
|
+
// Check if daemon is running
|
|
30
|
+
if (!isDaemonRunning()) {
|
|
31
|
+
printError('Daemon is not running. Start it with: jm2 start');
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!jobRef || jobRef.trim() === '') {
|
|
36
|
+
printError('Job ID or name is required');
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// Determine if jobRef is an ID (numeric) or name
|
|
42
|
+
const jobId = parseInt(jobRef, 10);
|
|
43
|
+
const message = isNaN(jobId)
|
|
44
|
+
? { type: MessageType.JOB_GET, jobName: jobRef }
|
|
45
|
+
: { type: MessageType.JOB_GET, jobId };
|
|
46
|
+
|
|
47
|
+
const response = await send(message);
|
|
48
|
+
|
|
49
|
+
if (response.type === MessageType.ERROR) {
|
|
50
|
+
printError(response.message);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (response.type === MessageType.JOB_GET_RESULT) {
|
|
55
|
+
const job = response.job;
|
|
56
|
+
|
|
57
|
+
if (!job) {
|
|
58
|
+
printError(`Job not found: ${jobRef}`);
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
printJobDetails(job);
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
printError('Unexpected response from daemon');
|
|
67
|
+
return 1;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
printError(`Failed to get job: ${error.message}`);
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Print detailed job information
|
|
76
|
+
* @param {object} job - Job object
|
|
77
|
+
*/
|
|
78
|
+
function printJobDetails(job) {
|
|
79
|
+
printHeader(`Job: ${job.name || job.id}`);
|
|
80
|
+
|
|
81
|
+
// Basic info
|
|
82
|
+
console.log(`${chalk.bold('ID:')} ${job.id}`);
|
|
83
|
+
console.log(`${chalk.bold('Name:')} ${job.name || chalk.gray('-')}`);
|
|
84
|
+
console.log(`${chalk.bold('Status:')} ${colorizeStatus(job.status)}`);
|
|
85
|
+
console.log(`${chalk.bold('Type:')} ${job.type || 'manual'}`);
|
|
86
|
+
|
|
87
|
+
// Schedule
|
|
88
|
+
console.log(`${chalk.bold('Schedule:')} ${formatJobSchedule(job)}`);
|
|
89
|
+
|
|
90
|
+
if (job.nextRun) {
|
|
91
|
+
console.log(`${chalk.bold('Next Run:')} ${formatDate(job.nextRun)} (${formatRelativeTime(job.nextRun)})`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (job.lastRun) {
|
|
95
|
+
console.log(`${chalk.bold('Last Run:')} ${formatDate(job.lastRun)} (${formatRelativeTime(job.lastRun)})`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Command
|
|
99
|
+
console.log();
|
|
100
|
+
console.log(`${chalk.bold('Command:')}`);
|
|
101
|
+
console.log(` ${job.command}`);
|
|
102
|
+
|
|
103
|
+
// Working directory
|
|
104
|
+
if (job.cwd) {
|
|
105
|
+
console.log();
|
|
106
|
+
console.log(`${chalk.bold('Working Directory:')}`);
|
|
107
|
+
console.log(` ${job.cwd}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Environment variables
|
|
111
|
+
if (job.env && Object.keys(job.env).length > 0) {
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(`${chalk.bold('Environment Variables:')}`);
|
|
114
|
+
for (const [key, value] of Object.entries(job.env)) {
|
|
115
|
+
console.log(` ${key}=${value}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Tags
|
|
120
|
+
if (job.tags && job.tags.length > 0) {
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(`${chalk.bold('Tags:')} ${job.tags.join(', ')}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Timeout and retry
|
|
126
|
+
if (job.timeout) {
|
|
127
|
+
console.log();
|
|
128
|
+
console.log(`${chalk.bold('Timeout:')} ${job.timeout}ms`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (job.retry > 0) {
|
|
132
|
+
console.log(`${chalk.bold('Retries:')} ${job.retry}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Metadata
|
|
136
|
+
console.log();
|
|
137
|
+
console.log(`${chalk.bold('Created:')} ${formatDate(job.createdAt)}`);
|
|
138
|
+
console.log(`${chalk.bold('Updated:')} ${formatDate(job.updatedAt)}`);
|
|
139
|
+
|
|
140
|
+
// Log file path
|
|
141
|
+
console.log();
|
|
142
|
+
const logFile = getJobLogFile(job.name || `job-${job.id}`);
|
|
143
|
+
console.log(`${chalk.bold('Log File:')} ${logFile}`);
|
|
144
|
+
|
|
145
|
+
// Execution info
|
|
146
|
+
if (job.lastExitCode !== undefined && job.lastExitCode !== null) {
|
|
147
|
+
const exitColor = job.lastExitCode === 0 ? chalk.green : chalk.red;
|
|
148
|
+
console.log();
|
|
149
|
+
console.log(`${chalk.bold('Last Exit Code:')} ${exitColor(job.lastExitCode)}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (job.retryCount > 0) {
|
|
153
|
+
console.log(`${chalk.bold('Retry Count:')} ${job.retryCount}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default { showCommand };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JM2 start command
|
|
3
|
+
* Starts the JM2 daemon process
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { startDaemon, isDaemonRunning, getDaemonStatus } from '../../daemon/index.js';
|
|
7
|
+
import { printSuccess, printError, printInfo } from '../utils/output.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Execute the start command
|
|
11
|
+
* @param {object} options - Command options
|
|
12
|
+
* @param {boolean} options.foreground - Run in foreground mode
|
|
13
|
+
* @returns {Promise<number>} Exit code
|
|
14
|
+
*/
|
|
15
|
+
export async function startCommand(options = {}) {
|
|
16
|
+
const { foreground = false } = options;
|
|
17
|
+
|
|
18
|
+
// Check if daemon is already running
|
|
19
|
+
if (isDaemonRunning()) {
|
|
20
|
+
const { pid } = getDaemonStatus();
|
|
21
|
+
printInfo(`Daemon is already running (PID: ${pid})`);
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
if (foreground) {
|
|
27
|
+
printInfo('Starting daemon in foreground mode...');
|
|
28
|
+
} else {
|
|
29
|
+
printInfo('Starting daemon...');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await startDaemon({ foreground });
|
|
33
|
+
|
|
34
|
+
if (!foreground) {
|
|
35
|
+
const { pid } = getDaemonStatus();
|
|
36
|
+
printSuccess(`Daemon started (PID: ${pid})`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return 0;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
printError(`Failed to start daemon: ${error.message}`);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default startCommand;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JM2 status command
|
|
3
|
+
* Shows the daemon status and statistics
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isDaemonRunning, getDaemonStatus } from '../../daemon/index.js';
|
|
7
|
+
import { getJobs } from '../../core/storage.js';
|
|
8
|
+
import {
|
|
9
|
+
colorizeDaemonStatus,
|
|
10
|
+
createStatusTable,
|
|
11
|
+
printHeader,
|
|
12
|
+
} from '../utils/output.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Execute the status command
|
|
16
|
+
* @param {object} _options - Command options (none for status)
|
|
17
|
+
* @returns {Promise<number>} Exit code
|
|
18
|
+
*/
|
|
19
|
+
export async function statusCommand(_options = {}) {
|
|
20
|
+
const running = isDaemonRunning();
|
|
21
|
+
const { pid } = getDaemonStatus();
|
|
22
|
+
|
|
23
|
+
// Count jobs
|
|
24
|
+
const jobs = getJobs();
|
|
25
|
+
const totalJobs = jobs.length;
|
|
26
|
+
const activeJobs = jobs.filter(j => j.status === 'active').length;
|
|
27
|
+
const pausedJobs = jobs.filter(j => j.status === 'paused').length;
|
|
28
|
+
|
|
29
|
+
// Print header
|
|
30
|
+
printHeader('JM2 Daemon Status');
|
|
31
|
+
|
|
32
|
+
// Create status table
|
|
33
|
+
const table = createStatusTable();
|
|
34
|
+
|
|
35
|
+
table.push(
|
|
36
|
+
['Status:', colorizeDaemonStatus(running)],
|
|
37
|
+
['PID:', pid ? pid.toString() : '-'],
|
|
38
|
+
['Jobs:', `${totalJobs} total (${activeJobs} active, ${pausedJobs} paused)`]
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
console.log(table.toString());
|
|
42
|
+
console.log();
|
|
43
|
+
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default statusCommand;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JM2 stop command
|
|
3
|
+
* Stops the JM2 daemon process
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { stopDaemon, isDaemonRunning, getDaemonStatus } from '../../daemon/index.js';
|
|
7
|
+
import { printSuccess, printError, printInfo, printWarning } from '../utils/output.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Execute the stop command
|
|
11
|
+
* @param {object} _options - Command options (none for stop)
|
|
12
|
+
* @returns {Promise<number>} Exit code
|
|
13
|
+
*/
|
|
14
|
+
export async function stopCommand(_options = {}) {
|
|
15
|
+
// Check if daemon is running
|
|
16
|
+
if (!isDaemonRunning()) {
|
|
17
|
+
printError('Daemon is not running');
|
|
18
|
+
return 3; // Specific exit code for daemon not running
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { pid } = getDaemonStatus();
|
|
22
|
+
printInfo(`Stopping daemon (PID: ${pid})...`);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const stopped = stopDaemon();
|
|
26
|
+
|
|
27
|
+
if (stopped) {
|
|
28
|
+
// Wait a moment to confirm the daemon stopped
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
30
|
+
|
|
31
|
+
if (!isDaemonRunning()) {
|
|
32
|
+
printSuccess('Daemon stopped');
|
|
33
|
+
return 0;
|
|
34
|
+
} else {
|
|
35
|
+
printWarning('Daemon may not have stopped cleanly');
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
printError('Failed to send stop signal to daemon');
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
printError(`Failed to stop daemon: ${error.message}`);
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default stopCommand;
|