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 +1 -1
- package/src/commands/start.ts +98 -17
- package/src/utils/array-parser.ts +16 -10
package/package.json
CHANGED
package/src/commands/start.ts
CHANGED
|
@@ -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
|
-
|
|
94
|
-
|
|
95
|
-
if
|
|
96
|
-
|
|
97
|
-
}
|
|
93
|
+
const platformInfo = getPlatformInfo();
|
|
94
|
+
|
|
95
|
+
// First check if service already exists
|
|
96
|
+
const serviceExists = await checkServiceExists(serviceName, platformInfo);
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
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
|
|
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, `${
|
|
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(
|
|
232
|
+
await createLinuxService(finalServiceName, execPath, host, port, protocol, options);
|
|
206
233
|
} else if (platformInfo.isMacOS) {
|
|
207
|
-
await createMacOSService(
|
|
234
|
+
await createMacOSService(finalServiceName, execPath, host, port, protocol, options);
|
|
208
235
|
} else if (platformInfo.isWindows) {
|
|
209
|
-
await createWindowsService(
|
|
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
|
-
|
|
285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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 =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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 [];
|