bs9 1.5.5 → 1.5.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bs9",
3
- "version": "1.5.5",
3
+ "version": "1.5.8",
4
4
  "description": "Bun Sentinel 9 - High-performance, non-root process manager for Bun",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -90,16 +90,27 @@ async function handleMultiServiceStart(file: string, options: StartOptions): Pro
90
90
  const results = await Promise.allSettled(
91
91
  services.map(async (serviceName) => {
92
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
- }
93
+ const platformInfo = getPlatformInfo();
94
+
95
+ // First check if service already exists
96
+ const serviceExists = await checkServiceExists(serviceName, platformInfo);
98
97
 
99
- await handleSingleServiceStart(serviceFile, { ...options, name: serviceName });
100
- return { service: serviceName, status: 'success', error: null };
98
+ if (serviceExists) {
99
+ // Service exists, start it directly
100
+ await startExistingService(serviceName, platformInfo);
101
+ return { service: serviceName, status: 'success', error: null };
102
+ } else {
103
+ // Service doesn't exist, look for file
104
+ const serviceFile = findServiceFile(serviceName);
105
+ if (!serviceFile) {
106
+ throw new Error(`Service file not found for: ${serviceName}`);
107
+ }
108
+
109
+ await handleSingleServiceStart(serviceFile, { ...options, name: serviceName });
110
+ return { service: serviceName, status: 'success', error: null };
111
+ }
101
112
  } catch (error) {
102
- return { service: serviceName, status: 'failed', error: error instanceof Error ? error.message : String(error) };
113
+ return { service: serviceName, status: 'failed', error: (error as Error).message };
103
114
  }
104
115
  })
105
116
  );
@@ -110,6 +121,22 @@ async function handleMultiServiceStart(file: string, options: StartOptions): Pro
110
121
  async function handleSingleServiceStart(file: string, options: StartOptions): Promise<void> {
111
122
  const platformInfo = getPlatformInfo();
112
123
 
124
+ // First, try to start existing service without file
125
+ const serviceName = options.name || file;
126
+
127
+ const serviceExists = await checkServiceExists(serviceName, platformInfo);
128
+
129
+ if (serviceExists) {
130
+ console.log(`šŸ“‹ Service '${serviceName}' already exists, starting...`);
131
+ try {
132
+ await startExistingService(serviceName, platformInfo);
133
+ return;
134
+ } catch (error) {
135
+ console.log(`āš ļø Failed to start existing service: ${error}`);
136
+ console.log(`šŸ“ Looking for application file: ${file}`);
137
+ }
138
+ }
139
+
113
140
  // Security: Validate and sanitize file path
114
141
  const fullPath = resolve(file);
115
142
  if (!existsSync(fullPath)) {
@@ -127,7 +154,7 @@ async function handleSingleServiceStart(file: string, options: StartOptions): Pr
127
154
 
128
155
  // Security: Validate and sanitize service name
129
156
  const rawServiceName = options.name || basename(fullPath, fullPath.endsWith('.ts') ? '.ts' : '.js');
130
- const serviceName = rawServiceName.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/^[^a-zA-Z]/, "_").substring(0, 64);
157
+ const finalServiceName = rawServiceName.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/^[^a-zA-Z]/, "_").substring(0, 64);
131
158
 
132
159
  // Security: Validate port number
133
160
  const port = options.port || "3000";
@@ -170,7 +197,7 @@ async function handleSingleServiceStart(file: string, options: StartOptions): Pr
170
197
  const buildDir = join(dirname(fullPath), ".bs9-build");
171
198
  mkdirSync(buildDir, { recursive: true });
172
199
 
173
- const outputFile = join(buildDir, `${serviceName}.js`);
200
+ const outputFile = join(buildDir, `${finalServiceName}.js`);
174
201
  try {
175
202
  execSync(`bun build ${fullPath} --outdir ${buildDir} --target bun --minify --splitting`, { stdio: "inherit" });
176
203
  execPath = outputFile;
@@ -202,17 +229,61 @@ async function handleSingleServiceStart(file: string, options: StartOptions): Pr
202
229
 
203
230
  // Platform-specific service creation
204
231
  if (platformInfo.isLinux) {
205
- await createLinuxService(serviceName, execPath, host, port, protocol, options);
232
+ await createLinuxService(finalServiceName, execPath, host, port, protocol, options);
206
233
  } else if (platformInfo.isMacOS) {
207
- await createMacOSService(serviceName, execPath, host, port, protocol, options);
234
+ await createMacOSService(finalServiceName, execPath, host, port, protocol, options);
208
235
  } else if (platformInfo.isWindows) {
209
- await createWindowsService(serviceName, execPath, host, port, protocol, options);
236
+ await createWindowsService(finalServiceName, execPath, host, port, protocol, options);
210
237
  } else {
211
238
  console.error(`āŒ Platform ${platformInfo.platform} is not supported`);
212
239
  process.exit(1);
213
240
  }
214
241
  }
215
242
 
243
+ async function checkServiceExists(serviceName: string, platformInfo: any): Promise<boolean> {
244
+ try {
245
+ if (platformInfo.isLinux) {
246
+ // Check if service exists in systemctl list-units
247
+ const listOutput = execSync("systemctl --user list-units --type=service --all --no-pager --no-legend", { encoding: "utf-8" });
248
+ const serviceExists = listOutput.includes(`${serviceName}.service`);
249
+ return serviceExists;
250
+ } else if (platformInfo.isMacOS) {
251
+ // Check if launchd service exists
252
+ const servicePath = join(platformInfo.serviceDir, `bs9.${serviceName}.plist`);
253
+ return existsSync(servicePath);
254
+ } else if (platformInfo.isWindows) {
255
+ // Check if Windows service exists
256
+ const { windowsCommand } = await import("../windows/service.js");
257
+ try {
258
+ await windowsCommand('show', { name: `BS9_${serviceName}` });
259
+ return true;
260
+ } catch {
261
+ return false;
262
+ }
263
+ }
264
+ return false;
265
+ } catch {
266
+ return false;
267
+ }
268
+ }
269
+
270
+ async function startExistingService(serviceName: string, platformInfo: any): Promise<void> {
271
+ try {
272
+ if (platformInfo.isLinux) {
273
+ execSync(`systemctl --user start ${serviceName}`, { stdio: "inherit" });
274
+ } else if (platformInfo.isMacOS) {
275
+ const { launchdCommand } = await import("../macos/launchd.js");
276
+ await launchdCommand('start', { name: `bs9.${serviceName}` });
277
+ } else if (platformInfo.isWindows) {
278
+ const { windowsCommand } = await import("../windows/service.js");
279
+ await windowsCommand('start', { name: `BS9_${serviceName}` });
280
+ }
281
+ console.log(`šŸš€ Service '${serviceName}' started successfully`);
282
+ } catch (error) {
283
+ throw error;
284
+ }
285
+ }
286
+
216
287
  function findServiceFile(serviceName: string): string | null {
217
288
  // Try to find the service file in common locations
218
289
  const possiblePaths = [
@@ -281,11 +352,21 @@ async function createLinuxService(serviceName: string, execPath: string, host: s
281
352
  }
282
353
 
283
354
  try {
284
- writeFileSync(unitPath, unitContent);
285
- console.log(`āœ… Systemd user unit written to: ${unitPath}`);
355
+ // Check if service already exists
356
+ const serviceExists = existsSync(unitPath);
357
+
358
+ if (!serviceExists) {
359
+ // First time: Create service file
360
+ writeFileSync(unitPath, unitContent);
361
+ console.log(`āœ… Systemd user unit written to: ${unitPath}`);
362
+ execSync("systemctl --user daemon-reload");
363
+ execSync(`systemctl --user enable ${serviceName}`);
364
+ console.log(`šŸ”§ Service '${serviceName}' created and enabled`);
365
+ } else {
366
+ console.log(`šŸ“‹ Service '${serviceName}' already exists, starting...`);
367
+ }
286
368
 
287
- execSync("systemctl --user daemon-reload");
288
- execSync(`systemctl --user enable ${serviceName}`);
369
+ // Always start the service
289
370
  execSync(`systemctl --user start ${serviceName}`);
290
371
 
291
372
  console.log(`šŸš€ Service '${serviceName}' started successfully`);
@@ -47,16 +47,20 @@ export async function parseServiceArray(input: string): Promise<string[]> {
47
47
 
48
48
  export async function getAllServices(): Promise<string[]> {
49
49
  try {
50
- const output = execSync("systemctl --user list-units --type=service --all --no-pager --no-legend", {
51
- encoding: "utf-8"
52
- });
50
+ // Use the same logic as the status command since it already works
51
+ const output = execSync("systemctl --user list-units --type=service --all --no-pager --no-legend", { encoding: "utf-8" });
52
+ const lines = output.split("\n").filter(line => line.includes(".service"));
53
53
 
54
- const services = output
55
- .split('\n')
56
- .filter(line => line.trim())
57
- .map(line => line.split(' ')[0])
58
- .filter(service => service.includes('bs9-'))
59
- .map(service => service.replace('bs9-', ''));
54
+ const services = [];
55
+ for (const line of lines) {
56
+ const match = line.match(/^(?:\s*([ā—\sā—‹]))?\s*([^\s]+)\.service\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+(.+)$/);
57
+ if (match) {
58
+ const [, , name, , , , description] = match;
59
+ if (description.includes("BS9 Service:")) {
60
+ services.push(name);
61
+ }
62
+ }
63
+ }
60
64
 
61
65
  return services;
62
66
  } catch {
@@ -67,7 +71,9 @@ export async function getAllServices(): Promise<string[]> {
67
71
  export async function getServicesByPattern(pattern: string): Promise<string[]> {
68
72
  try {
69
73
  const allServices = await getAllServices();
70
- const regex = new RegExp(`^${pattern}$`);
74
+ // Convert glob pattern to regex
75
+ const regexPattern = pattern.replace(/\*/g, '.*');
76
+ const regex = new RegExp(`^${regexPattern}$`);
71
77
  return allServices.filter(service => regex.test(service));
72
78
  } catch {
73
79
  return [];