@ts47andres/exeggutor 1.1.2

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.
@@ -0,0 +1,139 @@
1
+ // Exeggutor Server Manager.
2
+ // Starts the backend process as a truly detached background service.
3
+ // On Windows, uses a VBS launcher to avoid job-object child killing.
4
+ // On Unix, uses standard spawn with detached.
5
+
6
+ const { spawn, execSync, exec } = require('child_process');
7
+ const { existsSync, writeFileSync } = require('fs');
8
+ const { resolve } = require('path');
9
+ const { createServer } = require('net');
10
+ const os = require('os');
11
+
12
+ const IS_WIN = process.platform === 'win32';
13
+
14
+ // Finds the first available port starting from the preferred port.
15
+ function findAvailablePort(preferred) {
16
+ return new Promise((resolve) => {
17
+ const server = createServer();
18
+ const tryPort = (port) => {
19
+ server.listen(port, '127.0.0.1', () => {
20
+ server.close(() => resolve(port));
21
+ });
22
+ server.on('error', () => {
23
+ tryPort(port + 1);
24
+ });
25
+ };
26
+ tryPort(preferred);
27
+ });
28
+ }
29
+
30
+ // Resolves the JS entry for the backend process.
31
+ function resolveBackendPath(backendPath) {
32
+ const compiled = resolve(backendPath, 'dist', 'index.js');
33
+ if (existsSync(compiled)) return compiled;
34
+ const tsNodeEntry = resolve(backendPath, 'node_modules', 'ts-node-dev', 'lib', 'bin.js');
35
+ if (existsSync(tsNodeEntry)) return tsNodeEntry;
36
+ return null;
37
+ }
38
+
39
+ // Starts the backend server as a detached background process.
40
+ function startServers(root, config, logDir, tailscaleMode) {
41
+ const backendPort = config.backendPort || 17492; // Target port where the server daemon listens.
42
+ const backendPath = resolve(root, 'packages', 'backend'); // Core absolute path of the backend source files.
43
+ const entry = resolveBackendPath(backendPath); // Backend executable entry file reference.
44
+ if (!entry) {
45
+ console.error('[MGR] Backend entry not found. Ensure backend is built.');
46
+ return null;
47
+ }
48
+
49
+ const backendArgs = entry.endsWith('bin.js')
50
+ ? ['--respawn', '--transpile-only', 'src/index.ts']
51
+ : []; // Arguments list parsed to the spawned node runtime.
52
+
53
+ console.log(`[MGR] Starting backend: entry="${entry}" args=[${backendArgs.join(', ')}] port=${backendPort}`);
54
+
55
+ const env = {
56
+ ...process.env,
57
+ EXEGGUTOR_BACKEND_PORT: String(backendPort),
58
+ EXEGGUTOR_FRONTEND_DIST: resolve(root, 'packages', 'frontend', 'dist'),
59
+ }; // Combined environment options passed to the spawned process.
60
+
61
+ if (tailscaleMode) {
62
+ env.EXEGGUTOR_TAILSCALE = '1'; // Signal the backend to bind to 0.0.0.0 for Tailscale access.
63
+ }
64
+
65
+ const backendLog = resolve(logDir, 'backend.log'); // Logging path mapping for server output.
66
+ const fs = require('fs'); // Native file system module reference.
67
+ const logFd = fs.openSync(backendLog, 'a'); // Open log file in append mode.
68
+
69
+ // Write a startup marker to the log so we can identify process restarts.
70
+ fs.writeSync(logFd, `\n=== [MGR] Backend starting at ${new Date().toISOString()} ===\n`);
71
+
72
+ let child; // Reference container of the child background process.
73
+ child = spawn(process.execPath, [entry, ...backendArgs], {
74
+ cwd: backendPath,
75
+ env,
76
+ stdio: ['ignore', logFd, logFd],
77
+ detached: true,
78
+ windowsHide: true,
79
+ }); // Spawn the process in a cross-platform detached state.
80
+
81
+ fs.closeSync(logFd); // Close parent reference to log file descriptor.
82
+ child.unref();
83
+
84
+ console.log(`[MGR] Backend started: PID=${child.pid}`);
85
+
86
+ const cfgPath = resolve(os.homedir(), '.exeggutor.json'); // Absolute path to the runtime configuration file.
87
+ try {
88
+ const cfgContent = existsSync(cfgPath) ? JSON.parse(fs.readFileSync(cfgPath, 'utf8')) : {}; // Parsed config options.
89
+ cfgContent.backendPid = child.pid;
90
+ writeFileSync(cfgPath, JSON.stringify(cfgContent, null, 2), 'utf8');
91
+ } catch {}
92
+
93
+ config.backendPid = child.pid;
94
+ return { backendPid: child.pid, frontendPid: null };
95
+ }
96
+
97
+ // Stops the backend server.
98
+ function stopServers(config) {
99
+ const pid = config.backendPid;
100
+ if (pid) {
101
+ try {
102
+ if (IS_WIN) {
103
+ console.log(`[MGR] Stopping backend PID=${pid} via taskkill /F`);
104
+ execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
105
+ } else {
106
+ console.log(`[MGR] Stopping backend PID=${pid} via kill -9`);
107
+ execSync(`kill -9 ${pid}`, { stdio: 'ignore' });
108
+ }
109
+ console.log(`[MGR] Stopped Exeggutor (PID ${pid})`);
110
+ return;
111
+ } catch (err) {
112
+ console.log(`[MGR] taskkill/kill failed for PID ${pid}: ${err.message}`);
113
+ }
114
+ }
115
+ // Fallback: kill any process on our port
116
+ try {
117
+ if (IS_WIN) {
118
+ console.log(`[MGR] Fallback: killing process on port ${config.backendPort || 17492} via netstat + taskkill`);
119
+ const result = execSync(`netstat -ano | findstr ":${config.backendPort || 17492}"`, { encoding: 'utf8', timeout: 5000 });
120
+ const lines = result.trim().split('\n').filter(l => l.includes('LISTENING'));
121
+ for (const line of lines) {
122
+ const parts = line.trim().split(/\s+/);
123
+ const foundPid = parseInt(parts[parts.length - 1], 10);
124
+ if (!isNaN(foundPid)) {
125
+ console.log(`[MGR] Fallback: taskkill /F /PID ${foundPid}`);
126
+ execSync(`taskkill /F /PID ${foundPid}`, { stdio: 'ignore' });
127
+ console.log(`[MGR] Stopped Exeggutor (PID ${foundPid})`);
128
+ }
129
+ }
130
+ } else {
131
+ console.log(`[MGR] Fallback: killing process on port ${config.backendPort || 17492} via lsof + kill`);
132
+ execSync(`lsof -ti:${config.backendPort || 17492} | xargs kill -9 2>/dev/null`, { stdio: 'ignore' });
133
+ }
134
+ } catch {
135
+ console.log('[MGR] (No running Exeggutor server found)');
136
+ }
137
+ }
138
+
139
+ module.exports = { startServers, stopServers, findAvailablePort };