bs9 1.4.6 → 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.
@@ -11,6 +11,7 @@
11
11
 
12
12
  import { execSync } from "node:child_process";
13
13
  import { getPlatformInfo } from "../platform/detect.js";
14
+ import { parseServiceArray, confirmAction } from "../utils/array-parser.js";
14
15
 
15
16
  // Security: Service name validation
16
17
  function isValidServiceName(name: string): boolean {
@@ -18,7 +19,57 @@ function isValidServiceName(name: string): boolean {
18
19
  return validPattern.test(name) && name.length <= 64 && !name.includes('..') && !name.includes('/');
19
20
  }
20
21
 
21
- export async function restartCommand(name: string): Promise<void> {
22
+ export async function restartCommand(names: string[]): Promise<void> {
23
+ // Handle multiple arguments
24
+ const name = names.length > 0 ? names.join(' ') : '';
25
+
26
+ // Handle multi-service operations
27
+ if (name.includes('[') || name === 'all') {
28
+ await handleMultiServiceRestart(name);
29
+ return;
30
+ }
31
+
32
+ // Single service operation (existing logic)
33
+ await handleSingleServiceRestart(name);
34
+ }
35
+
36
+ async function handleMultiServiceRestart(name: string): Promise<void> {
37
+ const services = await parseServiceArray(name);
38
+
39
+ if (services.length === 0) {
40
+ console.log("❌ No services found matching the pattern");
41
+ return;
42
+ }
43
+
44
+ // Safety confirmation for bulk operations
45
+ if (services.length > 1) {
46
+ console.log(`⚠️ About to restart ${services.length} services:`);
47
+ services.forEach(service => console.log(` - ${service}`));
48
+
49
+ const confirmed = await confirmAction('Are you sure? (y/N): ');
50
+ if (!confirmed) {
51
+ console.log('❌ Restart operation cancelled');
52
+ return;
53
+ }
54
+ }
55
+
56
+ console.log(`🔄 Restarting ${services.length} services...`);
57
+
58
+ const results = await Promise.allSettled(
59
+ services.map(async (serviceName) => {
60
+ try {
61
+ await handleSingleServiceRestart(serviceName);
62
+ return { service: serviceName, status: 'success', error: null };
63
+ } catch (error) {
64
+ return { service: serviceName, status: 'failed', error: error instanceof Error ? error.message : String(error) };
65
+ }
66
+ })
67
+ );
68
+
69
+ displayBatchResults(results, 'restart');
70
+ }
71
+
72
+ async function handleSingleServiceRestart(name: string): Promise<void> {
22
73
  // Security: Validate service name
23
74
  if (!isValidServiceName(name)) {
24
75
  console.error(`❌ Security: Invalid service name: ${name}`);
@@ -45,3 +96,28 @@ export async function restartCommand(name: string): Promise<void> {
45
96
  process.exit(1);
46
97
  }
47
98
  }
99
+
100
+ function displayBatchResults(results: PromiseSettledResult<{ service: string; status: string; error: string | null }>[], operation: string): void {
101
+ console.log(`\n📊 Batch ${operation} Results`);
102
+ console.log("=".repeat(50));
103
+
104
+ const successful = results.filter(r => r.status === 'fulfilled' && r.value.status === 'success');
105
+ const failed = results.filter(r => r.status === 'fulfilled' && r.value.status === 'failed');
106
+
107
+ successful.forEach(result => {
108
+ if (result.status === 'fulfilled') {
109
+ console.log(`✅ ${result.value.service} - ${operation} successful`);
110
+ }
111
+ });
112
+
113
+ failed.forEach(result => {
114
+ if (result.status === 'fulfilled') {
115
+ console.log(`❌ ${result.value.service} - Failed: ${result.value.error}`);
116
+ }
117
+ });
118
+
119
+ console.log(`\n📈 Summary:`);
120
+ console.log(` Total: ${results.length} services`);
121
+ console.log(` Success: ${successful.length}/${results.length} (${((successful.length / results.length) * 100).toFixed(1)}%)`);
122
+ console.log(` Failed: ${failed.length}/${results.length} (${((failed.length / results.length) * 100).toFixed(1)}%)`);
123
+ }
@@ -16,6 +16,7 @@ import { randomUUID } from "node:crypto";
16
16
  import { writeFileSync, mkdirSync } from "node:fs";
17
17
  import { homedir } from "node:os";
18
18
  import { getPlatformInfo } from "../platform/detect.js";
19
+ import { parseServiceArray, getMultipleServiceInfo, confirmAction } from "../utils/array-parser.js";
19
20
 
20
21
  // Security: Host validation function
21
22
  function isValidHost(host: string): boolean {
@@ -60,7 +61,53 @@ interface StartOptions {
60
61
  https?: boolean;
61
62
  }
62
63
 
63
- export async function startCommand(file: string, options: StartOptions): Promise<void> {
64
+ export async function startCommand(files: string[], options: StartOptions): Promise<void> {
65
+ const platformInfo = getPlatformInfo();
66
+
67
+ // Handle multiple arguments
68
+ const file = files.length > 0 ? files.join(' ') : '';
69
+
70
+ // Handle multi-service operations
71
+ if (file.includes('[') || file === 'all') {
72
+ await handleMultiServiceStart(file, options);
73
+ return;
74
+ }
75
+
76
+ // Single service operation (existing logic)
77
+ await handleSingleServiceStart(file, options);
78
+ }
79
+
80
+ async function handleMultiServiceStart(file: string, options: StartOptions): Promise<void> {
81
+ const services = await parseServiceArray(file);
82
+
83
+ if (services.length === 0) {
84
+ console.log("❌ No services found matching the pattern");
85
+ return;
86
+ }
87
+
88
+ console.log(`🚀 Starting ${services.length} services...`);
89
+
90
+ const results = await Promise.allSettled(
91
+ services.map(async (serviceName) => {
92
+ try {
93
+ // For multi-service, we need to find the service file
94
+ const serviceFile = findServiceFile(serviceName);
95
+ if (!serviceFile) {
96
+ throw new Error(`Service file not found for: ${serviceName}`);
97
+ }
98
+
99
+ await handleSingleServiceStart(serviceFile, { ...options, name: serviceName });
100
+ return { service: serviceName, status: 'success', error: null };
101
+ } catch (error) {
102
+ return { service: serviceName, status: 'failed', error: error instanceof Error ? error.message : String(error) };
103
+ }
104
+ })
105
+ );
106
+
107
+ displayBatchResults(results, 'start');
108
+ }
109
+
110
+ async function handleSingleServiceStart(file: string, options: StartOptions): Promise<void> {
64
111
  const platformInfo = getPlatformInfo();
65
112
 
66
113
  // Security: Validate and sanitize file path
@@ -166,6 +213,51 @@ export async function startCommand(file: string, options: StartOptions): Promise
166
213
  }
167
214
  }
168
215
 
216
+ function findServiceFile(serviceName: string): string | null {
217
+ // Try to find the service file in common locations
218
+ const possiblePaths = [
219
+ join(process.cwd(), `${serviceName}.js`),
220
+ join(process.cwd(), `${serviceName}.ts`),
221
+ join(process.cwd(), 'src', `${serviceName}.js`),
222
+ join(process.cwd(), 'src', `${serviceName}.ts`),
223
+ join(process.cwd(), 'app', `${serviceName}.js`),
224
+ join(process.cwd(), 'app', `${serviceName}.ts`),
225
+ ];
226
+
227
+ for (const path of possiblePaths) {
228
+ if (existsSync(path)) {
229
+ return path;
230
+ }
231
+ }
232
+
233
+ return null;
234
+ }
235
+
236
+ function displayBatchResults(results: PromiseSettledResult<{ service: string; status: string; error: string | null }>[], operation: string): void {
237
+ console.log(`\n📊 Batch ${operation} Results`);
238
+ console.log("=".repeat(50));
239
+
240
+ const successful = results.filter(r => r.status === 'fulfilled' && r.value.status === 'success');
241
+ const failed = results.filter(r => r.status === 'fulfilled' && r.value.status === 'failed');
242
+
243
+ successful.forEach(result => {
244
+ if (result.status === 'fulfilled') {
245
+ console.log(`✅ ${result.value.service} - ${operation} successful`);
246
+ }
247
+ });
248
+
249
+ failed.forEach(result => {
250
+ if (result.status === 'fulfilled') {
251
+ console.log(`❌ ${result.value.service} - Failed: ${result.value.error}`);
252
+ }
253
+ });
254
+
255
+ console.log(`\n📈 Summary:`);
256
+ console.log(` Total: ${results.length} services`);
257
+ console.log(` Success: ${successful.length}/${results.length} (${((successful.length / results.length) * 100).toFixed(1)}%)`);
258
+ console.log(` Failed: ${failed.length}/${results.length} (${((failed.length / results.length) * 100).toFixed(1)}%)`);
259
+ }
260
+
169
261
  async function createLinuxService(serviceName: string, execPath: string, host: string, port: string, protocol: string, options: StartOptions): Promise<void> {
170
262
  // Phase 1: Generate hardened systemd unit
171
263
  const unitContent = generateSystemdUnit({
@@ -13,6 +13,7 @@ import { execSync } from "node:child_process";
13
13
  import { join } from "node:path";
14
14
  import { getPlatformInfo } from "../platform/detect.js";
15
15
  import { readFileSync } from "node:fs";
16
+ import { parseServiceArray, getMultipleServiceInfo } from "../utils/array-parser.js";
16
17
 
17
18
  interface StatusOptions {
18
19
  watch?: boolean;
@@ -34,7 +35,56 @@ interface ServiceStatus {
34
35
  lastRestart?: string;
35
36
  }
36
37
 
37
- export async function statusCommand(options: StatusOptions): Promise<void> {
38
+ export async function statusCommand(options: StatusOptions, names?: string[]): Promise<void> {
39
+ const platformInfo = getPlatformInfo();
40
+
41
+ // Handle multiple arguments
42
+ const name = names && names.length > 0 ? names.join(' ') : undefined;
43
+
44
+ // Handle multi-service status
45
+ if (name && (name.includes('[') || name === 'all')) {
46
+ await handleMultiServiceStatus(name, options);
47
+ return;
48
+ }
49
+
50
+ // Single service or all services status (existing logic)
51
+ await handleStatus(options, name);
52
+ }
53
+
54
+ async function handleMultiServiceStatus(name: string, options: StatusOptions): Promise<void> {
55
+ const services = await parseServiceArray(name);
56
+
57
+ if (services.length === 0) {
58
+ console.log("❌ No services found matching the pattern");
59
+ return;
60
+ }
61
+
62
+ console.log(`📊 Multi-Service Status: ${name}`);
63
+ console.log("=".repeat(80));
64
+
65
+ const serviceInfo = await getMultipleServiceInfo(services);
66
+
67
+ if (serviceInfo.length === 0) {
68
+ console.log("❌ No running services found");
69
+ return;
70
+ }
71
+
72
+ displayMultiServiceStatus(serviceInfo, name);
73
+
74
+ if (options.watch) {
75
+ console.log("\n🔄 Watching for changes (Ctrl+C to stop)...");
76
+ setInterval(async () => {
77
+ console.clear();
78
+ console.log(`📊 Multi-Service Status: ${name}`);
79
+ console.log("=".repeat(80));
80
+
81
+ const updatedServiceInfo = await getMultipleServiceInfo(services);
82
+ displayMultiServiceStatus(updatedServiceInfo, name);
83
+ }, 2000);
84
+ }
85
+ }
86
+
87
+ async function handleStatus(options: StatusOptions, name?: string): Promise<void> {
38
88
  const platformInfo = getPlatformInfo();
39
89
 
40
90
  try {
@@ -48,6 +98,11 @@ export async function statusCommand(options: StatusOptions): Promise<void> {
48
98
  services = await getWindowsServices();
49
99
  }
50
100
 
101
+ // Filter by specific service if provided
102
+ if (name) {
103
+ services = services.filter(service => service.name === name);
104
+ }
105
+
51
106
  displayServices(services);
52
107
 
53
108
  if (options.watch) {
@@ -66,15 +121,120 @@ export async function statusCommand(options: StatusOptions): Promise<void> {
66
121
  updatedServices = await getWindowsServices();
67
122
  }
68
123
 
124
+ // Filter by specific service if provided
125
+ if (name) {
126
+ updatedServices = updatedServices.filter(service => service.name === name);
127
+ }
128
+
69
129
  displayServices(updatedServices);
70
130
  }, 2000);
71
131
  }
72
- } catch (err) {
73
- console.error("❌ Failed to get service status:", err);
132
+ } catch (error) {
133
+ console.error("❌ Failed to get service status:", error);
74
134
  process.exit(1);
75
135
  }
76
136
  }
77
137
 
138
+ function displayServices(services: ServiceStatus[]): void {
139
+ if (services.length === 0) {
140
+ console.log("📋 No BS9 services found");
141
+ console.log("💡 Use 'bs9 start <file>' or 'bs9 deploy <file>' to create a service");
142
+ return;
143
+ }
144
+
145
+ // Header with better formatting
146
+ console.log(`${"SERVICE".padEnd(18)} ${"STATUS".padEnd(15)} ${"CPU".padEnd(10)} ${"MEMORY".padEnd(12)} ${"UPTIME".padEnd(12)} ${"TASKS".padEnd(8)} DESCRIPTION`);
147
+ console.log("─".repeat(100));
148
+
149
+ // Sort services by status (running first, then by name)
150
+ const sortedServices = services.sort((a, b) => {
151
+ const aRunning = a.active === "active" && a.sub === "running";
152
+ const bRunning = b.active === "active" && a.sub === "running";
153
+ if (aRunning !== bRunning) return bRunning ? 1 : -1;
154
+ return a.name.localeCompare(b.name);
155
+ });
156
+
157
+ for (const svc of sortedServices) {
158
+ // Better status formatting with indicators
159
+ let statusIndicator = "";
160
+ let status = `${svc.active}/${svc.sub}`;
161
+
162
+ if (svc.active === "active" && svc.sub === "running") {
163
+ statusIndicator = "✅";
164
+ status = "running";
165
+ } else if (svc.active === "activating" && svc.sub.includes("auto-restart")) {
166
+ statusIndicator = "🔄";
167
+ status = "restarting";
168
+ } else if (svc.active === "failed" || svc.sub === "failed") {
169
+ statusIndicator = "❌";
170
+ status = "failed";
171
+ } else if (svc.active === "inactive") {
172
+ statusIndicator = "⏸️";
173
+ status = "stopped";
174
+ } else {
175
+ statusIndicator = "⚠️";
176
+ }
177
+
178
+ const displayStatus = `${statusIndicator} ${status}`;
179
+
180
+ // Format memory and uptime better
181
+ const formattedMemory = svc.memory ? formatMemory(parseMemory(svc.memory)) : "-";
182
+ const formattedUptime = svc.uptime || "-";
183
+ const formattedCPU = svc.cpu || "-";
184
+
185
+ console.log(
186
+ `${svc.name.padEnd(18)} ${displayStatus.padEnd(15)} ${formattedCPU.padEnd(10)} ${formattedMemory.padEnd(12)} ${formattedUptime.padEnd(12)} ${(svc.tasks || "-").padEnd(8)} ${svc.description}`
187
+ );
188
+ }
189
+
190
+ // Enhanced summary with better formatting
191
+ console.log("\n📊 Service Summary:");
192
+ const totalServices = services.length;
193
+ const runningServices = services.filter(s => s.active === "active" && s.sub === "running").length;
194
+ const failedServices = services.filter(s => s.active === "failed" || s.sub === "failed").length;
195
+ const restartingServices = services.filter(s => s.active === "activating" && s.sub.includes("auto-restart")).length;
196
+ const totalMemory = services.reduce((sum, s) => sum + (s.memory ? parseMemory(s.memory) : 0), 0);
197
+
198
+ console.log(` 📈 Status: ${runningServices} running, ${failedServices} failed, ${restartingServices} restarting`);
199
+ console.log(` 📦 Total: ${runningServices}/${totalServices} services running`);
200
+ console.log(` 💾 Memory: ${formatMemory(totalMemory)}`);
201
+ console.log(` 🕒 Last updated: ${new Date().toLocaleString()}`);
202
+
203
+ // Show failed services details with better formatting
204
+ if (failedServices > 0) {
205
+ console.log("\n🚨 Failed Services:");
206
+ console.log("-".repeat(80));
207
+
208
+ services.filter(s => s.active === "failed" || s.sub === "failed").forEach(svc => {
209
+ console.log(` ❌ ${svc.name} - ${svc.description}`);
210
+ });
211
+ }
212
+ }
213
+
214
+ function displayMultiServiceStatus(serviceInfo: any[], pattern: string): void {
215
+ const running = serviceInfo.filter(s => s.status === 'active');
216
+ const failed = serviceInfo.filter(s => s.status === 'failed');
217
+ const inactive = serviceInfo.filter(s => s.status === 'inactive');
218
+
219
+ console.log(`\n📊 Services matching pattern: ${pattern}`);
220
+ console.log(` Total: ${serviceInfo.length} services`);
221
+ console.log(` Running: ${running.length}/${serviceInfo.length} (${((running.length / serviceInfo.length) * 100).toFixed(1)}%)`);
222
+ console.log(` Failed: ${failed.length}/${serviceInfo.length} (${((failed.length / serviceInfo.length) * 100).toFixed(1)}%)`);
223
+ console.log(` Inactive: ${inactive.length}/${serviceInfo.length} (${((inactive.length / serviceInfo.length) * 100).toFixed(1)}%)`);
224
+
225
+ if (serviceInfo.length > 0) {
226
+ console.log("\n📋 Service Details:");
227
+ console.log("-".repeat(80));
228
+
229
+ serviceInfo.forEach(service => {
230
+ const statusIcon = service.status === 'active' ? '✅' :
231
+ service.status === 'failed' ? '❌' : '⏸️';
232
+
233
+ console.log(`${statusIcon} ${service.name.padEnd(20)} PID: ${service.pid?.toString().padStart(8) || '-'.padStart(8)} PORT: ${service.port?.toString().padStart(6) || '-'.padStart(6)} STATUS: ${service.status.padEnd(10)}`);
234
+ });
235
+ }
236
+ }
237
+
78
238
  async function getLinuxServices(): Promise<ServiceStatus[]> {
79
239
  const services: ServiceStatus[] = [];
80
240
  const platformInfo = getPlatformInfo();
@@ -208,113 +368,6 @@ async function getWindowsServices(): Promise<ServiceStatus[]> {
208
368
  return services;
209
369
  }
210
370
 
211
- function displayServices(services: ServiceStatus[]): void {
212
- if (services.length === 0) {
213
- console.log("📋 No BS9 services found");
214
- console.log("💡 Use 'bs9 start <file>' or 'bs9 deploy <file>' to create a service");
215
- return;
216
- }
217
-
218
- // Header with better formatting
219
- console.log(`${"SERVICE".padEnd(18)} ${"STATUS".padEnd(15)} ${"CPU".padEnd(10)} ${"MEMORY".padEnd(12)} ${"UPTIME".padEnd(12)} ${"TASKS".padEnd(8)} DESCRIPTION`);
220
- console.log("─".repeat(100));
221
-
222
- // Sort services by status (running first, then by name)
223
- const sortedServices = services.sort((a, b) => {
224
- const aRunning = a.active === "active" && a.sub === "running";
225
- const bRunning = b.active === "active" && b.sub === "running";
226
- if (aRunning !== bRunning) return bRunning ? 1 : -1;
227
- return a.name.localeCompare(b.name);
228
- });
229
-
230
- for (const svc of sortedServices) {
231
- // Better status formatting with indicators
232
- let statusIndicator = "";
233
- let status = `${svc.active}/${svc.sub}`;
234
-
235
- if (svc.active === "active" && svc.sub === "running") {
236
- statusIndicator = "✅";
237
- status = "running";
238
- } else if (svc.active === "activating" && svc.sub.includes("auto-restart")) {
239
- statusIndicator = "🔄";
240
- status = "restarting";
241
- } else if (svc.active === "failed" || svc.sub === "failed") {
242
- statusIndicator = "❌";
243
- status = "failed";
244
- } else if (svc.active === "inactive") {
245
- statusIndicator = "⏸️";
246
- status = "stopped";
247
- } else {
248
- statusIndicator = "⚠️";
249
- }
250
-
251
- const displayStatus = `${statusIndicator} ${status}`;
252
-
253
- // Format memory and uptime better
254
- const formattedMemory = svc.memory ? formatMemory(parseMemory(svc.memory)) : "-";
255
- const formattedUptime = svc.uptime || "-";
256
- const formattedCPU = svc.cpu || "-";
257
-
258
- console.log(
259
- `${svc.name.padEnd(18)} ${displayStatus.padEnd(15)} ${formattedCPU.padEnd(10)} ${formattedMemory.padEnd(12)} ${formattedUptime.padEnd(12)} ${(svc.tasks || "-").padEnd(8)} ${svc.description}`
260
- );
261
- }
262
-
263
- // Enhanced summary with better formatting
264
- console.log("\n📊 Service Summary:");
265
- const totalServices = services.length;
266
- const runningServices = services.filter(s => s.active === "active" && s.sub === "running").length;
267
- const failedServices = services.filter(s => s.active === "failed" || s.sub === "failed").length;
268
- const restartingServices = services.filter(s => s.active === "activating" && s.sub.includes("auto-restart")).length;
269
- const totalMemory = services.reduce((sum, s) => sum + (s.memory ? parseMemory(s.memory) : 0), 0);
270
-
271
- console.log(` 📈 Status: ${runningServices} running, ${failedServices} failed, ${restartingServices} restarting`);
272
- console.log(` 📦 Total: ${runningServices}/${totalServices} services running`);
273
- console.log(` 💾 Memory: ${formatMemory(totalMemory)}`);
274
- console.log(` 🕒 Last updated: ${new Date().toLocaleString()}`);
275
-
276
- // Show failed services details with better formatting
277
- if (failedServices > 0) {
278
- console.log("\n❌ Failed Services:");
279
- const failed = services.filter(s => s.active === "failed" || s.sub === "failed");
280
- for (const svc of failed) {
281
- console.log(` • ${svc.name}: ${svc.active}/${svc.sub}`);
282
- console.log(` 💡 Troubleshoot: bs9 logs ${svc.name} --tail 20`);
283
- console.log(` 💡 Check: bs9 status ${svc.name}`);
284
- }
285
- }
286
-
287
- // Show restarting services details
288
- if (restartingServices > 0) {
289
- console.log("\n🔄 Restarting Services:");
290
- const restarting = services.filter(s => s.active === "activating" && s.sub.includes("auto-restart"));
291
- for (const svc of restarting) {
292
- console.log(` • ${svc.name}: ${svc.active}/${svc.sub}`);
293
- if (svc.exitCode) {
294
- console.log(` ❌ Exit Code: ${svc.exitCode}`);
295
- }
296
- if (svc.lastRestart) {
297
- console.log(` 🕒 Last Restart: ${svc.lastRestart}`);
298
- }
299
- console.log(` 💡 Troubleshoot: bs9 logs ${svc.name} --tail 20`);
300
- console.log(` 💡 Check: bs9 status ${svc.name}`);
301
- }
302
- }
303
-
304
- // Show running services details
305
- if (runningServices > 0) {
306
- console.log("\n✅ Running Services:");
307
- const running = services.filter(s => s.active === "active" && s.sub === "running");
308
- for (const svc of running) {
309
- const memory = svc.memory ? formatMemory(parseMemory(svc.memory)) : "N/A";
310
- const uptime = svc.uptime || "N/A";
311
- const port = svc.port || "3000";
312
- console.log(` • ${svc.name}: ${memory} memory, ${uptime} uptime`);
313
- console.log(` 🌐 Access: http://localhost:${port}`);
314
- }
315
- }
316
- }
317
-
318
371
  function formatCPU(nsec: number): string {
319
372
  const ms = nsec / 1_000_000;
320
373
  return `${ms.toFixed(1)}ms`;
@@ -12,6 +12,7 @@
12
12
  import { execSync } from "node:child_process";
13
13
  import { join } from "node:path";
14
14
  import { getPlatformInfo } from "../platform/detect.js";
15
+ import { parseServiceArray, confirmAction } from "../utils/array-parser.js";
15
16
 
16
17
  // Security: Service name validation
17
18
  function isValidServiceName(name: string): boolean {
@@ -21,7 +22,57 @@ function isValidServiceName(name: string): boolean {
21
22
  return validPattern.test(name) && name.length <= 64 && !name.includes('..') && !name.includes('/');
22
23
  }
23
24
 
24
- export async function stopCommand(name: string): Promise<void> {
25
+ export async function stopCommand(names: string[]): Promise<void> {
26
+ // Handle multiple arguments
27
+ const name = names.length > 0 ? names.join(' ') : '';
28
+
29
+ // Handle multi-service operations
30
+ if (name.includes('[') || name === 'all') {
31
+ await handleMultiServiceStop(name);
32
+ return;
33
+ }
34
+
35
+ // Single service operation (existing logic)
36
+ await handleSingleServiceStop(name);
37
+ }
38
+
39
+ async function handleMultiServiceStop(name: string): Promise<void> {
40
+ const services = await parseServiceArray(name);
41
+
42
+ if (services.length === 0) {
43
+ console.log("❌ No services found matching the pattern");
44
+ return;
45
+ }
46
+
47
+ // Safety confirmation for bulk operations
48
+ if (services.length > 1) {
49
+ console.log(`⚠️ About to stop ${services.length} services:`);
50
+ services.forEach(service => console.log(` - ${service}`));
51
+
52
+ const confirmed = await confirmAction('Are you sure? (y/N): ');
53
+ if (!confirmed) {
54
+ console.log('❌ Stop operation cancelled');
55
+ return;
56
+ }
57
+ }
58
+
59
+ console.log(`🛑 Stopping ${services.length} services...`);
60
+
61
+ const results = await Promise.allSettled(
62
+ services.map(async (serviceName) => {
63
+ try {
64
+ await handleSingleServiceStop(serviceName);
65
+ return { service: serviceName, status: 'success', error: null };
66
+ } catch (error) {
67
+ return { service: serviceName, status: 'failed', error: error instanceof Error ? error.message : String(error) };
68
+ }
69
+ })
70
+ );
71
+
72
+ displayBatchResults(results, 'stop');
73
+ }
74
+
75
+ async function handleSingleServiceStop(name: string): Promise<void> {
25
76
  // Security: Validate service name
26
77
  if (!isValidServiceName(name)) {
27
78
  console.error(`❌ Security: Invalid service name: ${name}`);
@@ -48,3 +99,28 @@ export async function stopCommand(name: string): Promise<void> {
48
99
  process.exit(1);
49
100
  }
50
101
  }
102
+
103
+ function displayBatchResults(results: PromiseSettledResult<{ service: string; status: string; error: string | null }>[], operation: string): void {
104
+ console.log(`\n📊 Batch ${operation} Results`);
105
+ console.log("=".repeat(50));
106
+
107
+ const successful = results.filter(r => r.status === 'fulfilled' && r.value.status === 'success');
108
+ const failed = results.filter(r => r.status === 'fulfilled' && r.value.status === 'failed');
109
+
110
+ successful.forEach(result => {
111
+ if (result.status === 'fulfilled') {
112
+ console.log(`✅ ${result.value.service} - ${operation} successful`);
113
+ }
114
+ });
115
+
116
+ failed.forEach(result => {
117
+ if (result.status === 'fulfilled') {
118
+ console.log(`❌ ${result.value.service} - Failed: ${result.value.error}`);
119
+ }
120
+ });
121
+
122
+ console.log(`\n📈 Summary:`);
123
+ console.log(` Total: ${results.length} services`);
124
+ console.log(` Success: ${successful.length}/${results.length} (${((successful.length / results.length) * 100).toFixed(1)}%)`);
125
+ console.log(` Failed: ${failed.length}/${results.length} (${((failed.length / results.length) * 100).toFixed(1)}%)`);
126
+ }