solforge 0.2.3 → 0.2.5
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/LICENSE +2 -2
- package/README.md +323 -364
- package/cli.cjs +126 -69
- package/package.json +1 -1
- package/scripts/install.sh +112 -0
- package/scripts/postinstall.cjs +66 -58
- package/server/methods/program/get-token-accounts-by-owner.ts +7 -2
- package/server/ws-server.ts +4 -1
- package/src/api-server-entry.ts +91 -91
- package/src/cli/commands/rpc-start.ts +4 -1
- package/src/cli/main.ts +39 -14
- package/src/cli/run-solforge.ts +20 -6
- package/src/commands/add-program.ts +324 -328
- package/src/commands/init.ts +106 -106
- package/src/commands/list.ts +125 -125
- package/src/commands/mint.ts +246 -246
- package/src/commands/start.ts +834 -831
- package/src/commands/status.ts +80 -80
- package/src/commands/stop.ts +381 -382
- package/src/config/manager.ts +149 -149
- package/src/gui/public/app.css +1556 -1
- package/src/gui/public/build/main.css +1569 -1
- package/src/gui/server.ts +20 -21
- package/src/gui/src/app.tsx +56 -37
- package/src/gui/src/components/airdrop-mint-form.tsx +17 -11
- package/src/gui/src/components/clone-program-modal.tsx +6 -6
- package/src/gui/src/components/clone-token-modal.tsx +7 -7
- package/src/gui/src/components/modal.tsx +13 -11
- package/src/gui/src/components/programs-panel.tsx +27 -15
- package/src/gui/src/components/status-panel.tsx +31 -17
- package/src/gui/src/components/tokens-panel.tsx +25 -19
- package/src/gui/src/index.css +491 -463
- package/src/index.ts +161 -146
- package/src/rpc/start.ts +1 -1
- package/src/services/api-server.ts +470 -473
- package/src/services/port-manager.ts +167 -167
- package/src/services/process-registry.ts +143 -143
- package/src/services/program-cloner.ts +312 -312
- package/src/services/token-cloner.ts +799 -797
- package/src/services/validator.ts +288 -288
- package/src/types/config.ts +71 -71
- package/src/utils/shell.ts +75 -75
- package/src/utils/token-loader.ts +77 -77
package/src/commands/stop.ts
CHANGED
|
@@ -1,406 +1,405 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { select } from "@inquirer/prompts";
|
|
3
|
-
import
|
|
4
|
-
|
|
2
|
+
import chalk from "chalk";
|
|
5
3
|
import type { RunningValidator } from "../services/process-registry.js";
|
|
4
|
+
import { processRegistry } from "../services/process-registry.js";
|
|
6
5
|
|
|
7
6
|
export async function stopCommand(
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
validatorId?: string,
|
|
8
|
+
options: { all?: boolean; kill?: boolean } = {},
|
|
10
9
|
): Promise<void> {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
10
|
+
console.log(chalk.blue("🛑 Stopping validator(s)...\n"));
|
|
11
|
+
|
|
12
|
+
// Clean up dead processes first
|
|
13
|
+
await processRegistry.cleanup();
|
|
14
|
+
|
|
15
|
+
const validators = processRegistry.getRunning();
|
|
16
|
+
|
|
17
|
+
if (validators.length === 0) {
|
|
18
|
+
console.log(chalk.yellow("⚠️ No running validators found"));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let validatorsToStop: RunningValidator[] = [];
|
|
23
|
+
|
|
24
|
+
if (options.all) {
|
|
25
|
+
// Stop all validators
|
|
26
|
+
validatorsToStop = validators;
|
|
27
|
+
console.log(
|
|
28
|
+
chalk.cyan(`🔄 Stopping all ${validators.length} validator(s)...`),
|
|
29
|
+
);
|
|
30
|
+
} else if (validatorId) {
|
|
31
|
+
// Stop specific validator
|
|
32
|
+
const validator = processRegistry.getById(validatorId);
|
|
33
|
+
if (!validator) {
|
|
34
|
+
console.error(
|
|
35
|
+
chalk.red(`❌ Validator with ID '${validatorId}' not found`),
|
|
36
|
+
);
|
|
37
|
+
console.log(
|
|
38
|
+
chalk.gray("💡 Use `solforge list` to see running validators"),
|
|
39
|
+
);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
validatorsToStop = [validator];
|
|
43
|
+
console.log(
|
|
44
|
+
chalk.cyan(
|
|
45
|
+
`🔄 Stopping validator '${validator.name}' (${validatorId})...`,
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
// No specific validator specified, show available options
|
|
50
|
+
console.log(chalk.yellow("⚠️ Please specify which validator to stop:"));
|
|
51
|
+
console.log(
|
|
52
|
+
chalk.gray("💡 Use `solforge stop <id>` to stop a specific validator"),
|
|
53
|
+
);
|
|
54
|
+
console.log(
|
|
55
|
+
chalk.gray("💡 Use `solforge stop --all` to stop all validators"),
|
|
56
|
+
);
|
|
57
|
+
console.log(chalk.gray("💡 Use `solforge list` to see running validators"));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Stop each validator
|
|
62
|
+
let stoppedCount = 0;
|
|
63
|
+
let errorCount = 0;
|
|
64
|
+
|
|
65
|
+
for (const validator of validatorsToStop) {
|
|
66
|
+
try {
|
|
67
|
+
const result = await stopValidator(validator, options.kill);
|
|
68
|
+
if (result.success) {
|
|
69
|
+
console.log(
|
|
70
|
+
chalk.green(`✅ Stopped ${validator.name} (${validator.id})`),
|
|
71
|
+
);
|
|
72
|
+
stoppedCount++;
|
|
73
|
+
} else {
|
|
74
|
+
console.error(
|
|
75
|
+
chalk.red(
|
|
76
|
+
`❌ Failed to stop ${validator.name} (${validator.id}): ${result.error}`,
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
errorCount++;
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(
|
|
83
|
+
chalk.red(
|
|
84
|
+
`❌ Error stopping ${validator.name} (${validator.id}): ${
|
|
85
|
+
error instanceof Error ? error.message : String(error)
|
|
86
|
+
}`,
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
errorCount++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Summary
|
|
94
|
+
console.log();
|
|
95
|
+
if (stoppedCount > 0) {
|
|
96
|
+
console.log(
|
|
97
|
+
chalk.green(`✅ Successfully stopped ${stoppedCount} validator(s)`),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (errorCount > 0) {
|
|
101
|
+
console.log(chalk.red(`❌ Failed to stop ${errorCount} validator(s)`));
|
|
102
|
+
}
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
async function stopValidator(
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
validator: RunningValidator,
|
|
107
|
+
forceKill: boolean = false,
|
|
109
108
|
): Promise<{ success: boolean; error?: string }> {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
109
|
+
try {
|
|
110
|
+
// Stop the API server first if it has a PID
|
|
111
|
+
if (validator.apiServerPid) {
|
|
112
|
+
try {
|
|
113
|
+
process.kill(validator.apiServerPid, "SIGTERM");
|
|
114
|
+
console.log(
|
|
115
|
+
chalk.gray(`📡 Stopped API server (PID: ${validator.apiServerPid})`),
|
|
116
|
+
);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.log(
|
|
119
|
+
chalk.yellow(
|
|
120
|
+
`⚠️ Warning: Failed to stop API server: ${
|
|
121
|
+
error instanceof Error ? error.message : String(error)
|
|
122
|
+
}`,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check if process is still running
|
|
129
|
+
const isRunning = await processRegistry.isProcessRunning(validator.pid);
|
|
130
|
+
if (!isRunning) {
|
|
131
|
+
// Process already stopped, just clean up registry
|
|
132
|
+
processRegistry.unregister(validator.id);
|
|
133
|
+
return { success: true };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const signal = forceKill ? "SIGKILL" : "SIGTERM";
|
|
137
|
+
|
|
138
|
+
// Send termination signal
|
|
139
|
+
process.kill(validator.pid, signal);
|
|
140
|
+
|
|
141
|
+
if (forceKill) {
|
|
142
|
+
// For SIGKILL, process should stop immediately
|
|
143
|
+
processRegistry.unregister(validator.id);
|
|
144
|
+
return { success: true };
|
|
145
|
+
} else {
|
|
146
|
+
// For SIGTERM, wait for graceful shutdown
|
|
147
|
+
const shutdownResult = await waitForProcessShutdown(validator.pid, 10000);
|
|
148
|
+
|
|
149
|
+
if (shutdownResult.success) {
|
|
150
|
+
processRegistry.unregister(validator.id);
|
|
151
|
+
return { success: true };
|
|
152
|
+
} else {
|
|
153
|
+
// Graceful shutdown failed, try force kill
|
|
154
|
+
console.log(
|
|
155
|
+
chalk.yellow(
|
|
156
|
+
`⚠️ Graceful shutdown failed for ${validator.name}, force killing...`,
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
process.kill(validator.pid, "SIGKILL");
|
|
160
|
+
processRegistry.unregister(validator.id);
|
|
161
|
+
return { success: true };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
166
|
+
|
|
167
|
+
// If error is "ESRCH" (No such process), the process is already gone
|
|
168
|
+
if (errorMessage.includes("ESRCH")) {
|
|
169
|
+
processRegistry.unregister(validator.id);
|
|
170
|
+
return { success: true };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return { success: false, error: errorMessage };
|
|
174
|
+
}
|
|
176
175
|
}
|
|
177
176
|
|
|
178
177
|
async function waitForProcessShutdown(
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
pid: number,
|
|
179
|
+
timeoutMs: number = 10000,
|
|
181
180
|
): Promise<{ success: boolean; error?: string }> {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
181
|
+
const startTime = Date.now();
|
|
182
|
+
|
|
183
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
184
|
+
try {
|
|
185
|
+
// Send signal 0 to check if process exists
|
|
186
|
+
process.kill(pid, 0);
|
|
187
|
+
// If no error thrown, process is still running
|
|
188
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
189
|
+
} catch (error) {
|
|
190
|
+
// Process is gone
|
|
191
|
+
return { success: true };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { success: false, error: "Process shutdown timeout" };
|
|
197
196
|
}
|
|
198
197
|
|
|
199
198
|
export async function killCommand(
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
validatorId?: string,
|
|
200
|
+
options: { all?: boolean } = {},
|
|
202
201
|
): Promise<void> {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
202
|
+
console.log(chalk.red("💀 Force killing validator(s)...\n"));
|
|
203
|
+
|
|
204
|
+
// Clean up dead processes first
|
|
205
|
+
await processRegistry.cleanup();
|
|
206
|
+
|
|
207
|
+
const validators = processRegistry.getRunning();
|
|
208
|
+
|
|
209
|
+
if (validators.length === 0) {
|
|
210
|
+
console.log(chalk.yellow("⚠️ No running validators found"));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let validatorsToKill: RunningValidator[] = [];
|
|
215
|
+
|
|
216
|
+
if (options.all) {
|
|
217
|
+
// Kill all validators
|
|
218
|
+
validatorsToKill = validators;
|
|
219
|
+
console.log(
|
|
220
|
+
chalk.cyan(`🔄 Force killing all ${validators.length} validator(s)...`),
|
|
221
|
+
);
|
|
222
|
+
} else if (validatorId) {
|
|
223
|
+
// Kill specific validator
|
|
224
|
+
const validator = processRegistry.getById(validatorId);
|
|
225
|
+
if (!validator) {
|
|
226
|
+
console.error(
|
|
227
|
+
chalk.red(`❌ Validator with ID '${validatorId}' not found`),
|
|
228
|
+
);
|
|
229
|
+
console.log(
|
|
230
|
+
chalk.gray("💡 Use `solforge list` to see running validators"),
|
|
231
|
+
);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
validatorsToKill = [validator];
|
|
235
|
+
console.log(
|
|
236
|
+
chalk.cyan(
|
|
237
|
+
`🔄 Force killing validator '${validator.name}' (${validatorId})...`,
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
} else {
|
|
241
|
+
// No specific validator specified, show interactive selection
|
|
242
|
+
console.log(chalk.cyan("📋 Select validator(s) to force kill:\n"));
|
|
243
|
+
|
|
244
|
+
// Display current validators
|
|
245
|
+
displayValidatorsTable(validators);
|
|
246
|
+
|
|
247
|
+
const choices = [
|
|
248
|
+
...validators.map((v) => ({
|
|
249
|
+
name: `${v.name} (${v.id}) - PID: ${v.pid}`,
|
|
250
|
+
value: v.id,
|
|
251
|
+
})),
|
|
252
|
+
{
|
|
253
|
+
name: chalk.red("Kill ALL validators"),
|
|
254
|
+
value: "all",
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: chalk.gray("Cancel"),
|
|
258
|
+
value: "cancel",
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
const selectedValidator = await select({
|
|
263
|
+
message: "Which validator would you like to force kill?",
|
|
264
|
+
choices,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (selectedValidator === "cancel") {
|
|
268
|
+
console.log(chalk.gray("Operation cancelled"));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (selectedValidator === "all") {
|
|
273
|
+
validatorsToKill = validators;
|
|
274
|
+
console.log(
|
|
275
|
+
chalk.cyan(`🔄 Force killing all ${validators.length} validator(s)...`),
|
|
276
|
+
);
|
|
277
|
+
} else {
|
|
278
|
+
const validator = processRegistry.getById(selectedValidator);
|
|
279
|
+
if (!validator) {
|
|
280
|
+
console.error(chalk.red("❌ Selected validator not found"));
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
validatorsToKill = [validator];
|
|
284
|
+
console.log(
|
|
285
|
+
chalk.cyan(
|
|
286
|
+
`🔄 Force killing validator '${validator.name}' (${selectedValidator})...`,
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Kill each validator
|
|
293
|
+
let killedCount = 0;
|
|
294
|
+
let errorCount = 0;
|
|
295
|
+
|
|
296
|
+
for (const validator of validatorsToKill) {
|
|
297
|
+
try {
|
|
298
|
+
const result = await stopValidator(validator, true); // Force kill
|
|
299
|
+
if (result.success) {
|
|
300
|
+
console.log(
|
|
301
|
+
chalk.green(`✅ Killed ${validator.name} (${validator.id})`),
|
|
302
|
+
);
|
|
303
|
+
killedCount++;
|
|
304
|
+
} else {
|
|
305
|
+
console.error(
|
|
306
|
+
chalk.red(
|
|
307
|
+
`❌ Failed to kill ${validator.name} (${validator.id}): ${result.error}`,
|
|
308
|
+
),
|
|
309
|
+
);
|
|
310
|
+
errorCount++;
|
|
311
|
+
}
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error(
|
|
314
|
+
chalk.red(
|
|
315
|
+
`❌ Error killing ${validator.name} (${validator.id}): ${
|
|
316
|
+
error instanceof Error ? error.message : String(error)
|
|
317
|
+
}`,
|
|
318
|
+
),
|
|
319
|
+
);
|
|
320
|
+
errorCount++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Summary
|
|
325
|
+
console.log();
|
|
326
|
+
if (killedCount > 0) {
|
|
327
|
+
console.log(
|
|
328
|
+
chalk.green(`✅ Successfully killed ${killedCount} validator(s)`),
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
if (errorCount > 0) {
|
|
332
|
+
console.log(chalk.red(`❌ Failed to kill ${errorCount} validator(s)`));
|
|
333
|
+
}
|
|
335
334
|
}
|
|
336
335
|
|
|
337
336
|
function displayValidatorsTable(validators: RunningValidator[]): void {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
337
|
+
// Calculate column widths
|
|
338
|
+
const maxIdWidth = Math.max(2, ...validators.map((v) => v.id.length));
|
|
339
|
+
const maxNameWidth = Math.max(4, ...validators.map((v) => v.name.length));
|
|
340
|
+
const maxPidWidth = Math.max(
|
|
341
|
+
3,
|
|
342
|
+
...validators.map((v) => v.pid.toString().length),
|
|
343
|
+
);
|
|
344
|
+
const maxPortWidth = 9; // "8899/9900" format
|
|
345
|
+
const maxUptimeWidth = 7;
|
|
346
|
+
|
|
347
|
+
// Header
|
|
348
|
+
const header =
|
|
349
|
+
chalk.bold("ID".padEnd(maxIdWidth)) +
|
|
350
|
+
" | " +
|
|
351
|
+
chalk.bold("Name".padEnd(maxNameWidth)) +
|
|
352
|
+
" | " +
|
|
353
|
+
chalk.bold("PID".padEnd(maxPidWidth)) +
|
|
354
|
+
" | " +
|
|
355
|
+
chalk.bold("RPC/Faucet".padEnd(maxPortWidth)) +
|
|
356
|
+
" | " +
|
|
357
|
+
chalk.bold("Uptime".padEnd(maxUptimeWidth)) +
|
|
358
|
+
" | " +
|
|
359
|
+
chalk.bold("Status");
|
|
360
|
+
|
|
361
|
+
console.log(header);
|
|
362
|
+
console.log("-".repeat(header.length - 20)); // Subtract ANSI codes length
|
|
363
|
+
|
|
364
|
+
// Rows
|
|
365
|
+
validators.forEach((validator) => {
|
|
366
|
+
const uptime = formatUptime(validator.startTime);
|
|
367
|
+
const ports = `${validator.rpcPort}/${validator.faucetPort}`;
|
|
368
|
+
const status =
|
|
369
|
+
validator.status === "running" ? chalk.green("●") : chalk.red("●");
|
|
370
|
+
|
|
371
|
+
const row =
|
|
372
|
+
validator.id.padEnd(maxIdWidth) +
|
|
373
|
+
" | " +
|
|
374
|
+
validator.name.padEnd(maxNameWidth) +
|
|
375
|
+
" | " +
|
|
376
|
+
validator.pid.toString().padEnd(maxPidWidth) +
|
|
377
|
+
" | " +
|
|
378
|
+
ports.padEnd(maxPortWidth) +
|
|
379
|
+
" | " +
|
|
380
|
+
uptime.padEnd(maxUptimeWidth) +
|
|
381
|
+
" | " +
|
|
382
|
+
status;
|
|
383
|
+
|
|
384
|
+
console.log(row);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
console.log(); // Empty line
|
|
389
388
|
}
|
|
390
389
|
|
|
391
390
|
function formatUptime(startTime: Date): string {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
391
|
+
const now = new Date();
|
|
392
|
+
const uptimeMs = now.getTime() - startTime.getTime();
|
|
393
|
+
const uptimeSeconds = Math.floor(uptimeMs / 1000);
|
|
394
|
+
|
|
395
|
+
if (uptimeSeconds < 60) {
|
|
396
|
+
return `${uptimeSeconds}s`;
|
|
397
|
+
} else if (uptimeSeconds < 3600) {
|
|
398
|
+
const minutes = Math.floor(uptimeSeconds / 60);
|
|
399
|
+
return `${minutes}m`;
|
|
400
|
+
} else {
|
|
401
|
+
const hours = Math.floor(uptimeSeconds / 3600);
|
|
402
|
+
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
|
|
403
|
+
return `${hours}h${minutes}m`;
|
|
404
|
+
}
|
|
406
405
|
}
|