cc-caffeine 0.2.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.
- package/.claude/settings.local.json +70 -0
- package/.claude-plugin/plugin.json +23 -0
- package/.eslintrc.json +36 -0
- package/.github/FUNDING.yml +1 -0
- package/.github/dependabot.yml +6 -0
- package/.github/workflows/build.yml +79 -0
- package/.prettierrc +11 -0
- package/CLAUDE.md +307 -0
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/assets/icon-coffee-empty.png +0 -0
- package/assets/icon-coffee-empty.svg +10 -0
- package/assets/icon-coffee-full.png +0 -0
- package/assets/icon-coffee-full.svg +15 -0
- package/caffeine.js +63 -0
- package/hooks/hooks.json +67 -0
- package/package.json +57 -0
- package/src/commands.js +168 -0
- package/src/electron.js +139 -0
- package/src/pid.js +224 -0
- package/src/server.js +182 -0
- package/src/session.js +227 -0
- package/src/system-tray.js +247 -0
package/src/electron.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Electron module - Handles all Electron-specific functionality
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
let electron, Tray, Menu, powerSaveBlocker, nativeImage, app, shell;
|
|
6
|
+
let isElectron = false;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Load Electron modules only when needed
|
|
10
|
+
*/
|
|
11
|
+
const loadElectron = () => {
|
|
12
|
+
if (isElectron) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
electron = require('electron');
|
|
18
|
+
Tray = electron.Tray;
|
|
19
|
+
Menu = electron.Menu;
|
|
20
|
+
powerSaveBlocker = electron.powerSaveBlocker;
|
|
21
|
+
nativeImage = electron.nativeImage;
|
|
22
|
+
app = electron.app;
|
|
23
|
+
shell = electron.shell;
|
|
24
|
+
isElectron = true;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('Failed to load Electron:', error.message);
|
|
27
|
+
console.error('Make sure to use Electron: npx electron caffeine.js server');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get Electron modules
|
|
34
|
+
*/
|
|
35
|
+
const getElectron = () => {
|
|
36
|
+
if (!isElectron) {
|
|
37
|
+
loadElectron();
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
electron,
|
|
41
|
+
Tray,
|
|
42
|
+
Menu,
|
|
43
|
+
powerSaveBlocker,
|
|
44
|
+
nativeImage,
|
|
45
|
+
app,
|
|
46
|
+
shell
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if running inside Electron
|
|
52
|
+
*/
|
|
53
|
+
const isRunningInElectron = () => {
|
|
54
|
+
return !!process.versions.electron;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Prevent any window from being created
|
|
59
|
+
*/
|
|
60
|
+
const preventWindowCreation = () => {
|
|
61
|
+
const { app } = getElectron();
|
|
62
|
+
|
|
63
|
+
if (app.dock && typeof app.dock.hide === 'function') {
|
|
64
|
+
try {
|
|
65
|
+
app.dock.hide();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Silently ignore if not macOS or other error
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
app.on('browser-window-created', (event, window) => {
|
|
72
|
+
window.hide();
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Setup Electron app event handlers
|
|
78
|
+
*/
|
|
79
|
+
const setupAppEventHandlers = shutdownCallback => {
|
|
80
|
+
const { app } = getElectron();
|
|
81
|
+
|
|
82
|
+
app.on('window-all-closed', () => {
|
|
83
|
+
// Don't quit on window close since we're running in background
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
app.on('before-quit', () => {
|
|
87
|
+
// Cleanup handled by shutdown callback
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
app.on('activate', () => {
|
|
91
|
+
// No window to restore, we're system tray only
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
process.on('SIGINT', () => {
|
|
95
|
+
if (shutdownCallback) {
|
|
96
|
+
shutdownCallback();
|
|
97
|
+
} else {
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
process.on('SIGTERM', () => {
|
|
103
|
+
if (shutdownCallback) {
|
|
104
|
+
shutdownCallback();
|
|
105
|
+
} else {
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Wait for Electron app to be ready
|
|
113
|
+
*/
|
|
114
|
+
const whenReady = () => {
|
|
115
|
+
const { app } = getElectron();
|
|
116
|
+
return app.whenReady();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Quit Electron app
|
|
121
|
+
*/
|
|
122
|
+
const quit = () => {
|
|
123
|
+
const { app } = getElectron();
|
|
124
|
+
if (app && typeof app.quit === 'function') {
|
|
125
|
+
app.quit();
|
|
126
|
+
} else {
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
loadElectron,
|
|
133
|
+
getElectron,
|
|
134
|
+
isRunningInElectron,
|
|
135
|
+
preventWindowCreation,
|
|
136
|
+
setupAppEventHandlers,
|
|
137
|
+
whenReady,
|
|
138
|
+
quit
|
|
139
|
+
};
|
package/src/pid.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PID management module - Handles atomic PID file operations and validation
|
|
5
|
+
*
|
|
6
|
+
* This module provides functions to:
|
|
7
|
+
* - Atomically read/write PID files
|
|
8
|
+
* - Validate if a PID belongs to a caffeine server process
|
|
9
|
+
* - Clean up stale PID files
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const { spawn } = require('child_process');
|
|
16
|
+
const lockfile = require('proper-lockfile');
|
|
17
|
+
|
|
18
|
+
const CONFIG_DIR = path.join(os.homedir(), '.claude', 'plugins', 'cc-caffeine');
|
|
19
|
+
const PID_FILE = path.join(CONFIG_DIR, 'server.pid');
|
|
20
|
+
|
|
21
|
+
const withPidLock = async (fn) => {
|
|
22
|
+
// create if not exists
|
|
23
|
+
try {
|
|
24
|
+
const fd = fs.openSync(PID_FILE, 'wx');
|
|
25
|
+
fs.closeSync(fd);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
if (err.code !== 'EEXIST') {
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
// If EEXIST, file already exists, nothing to do
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let output = null;
|
|
34
|
+
|
|
35
|
+
const release = await lockfile.lock(PID_FILE, {
|
|
36
|
+
retries: 3,
|
|
37
|
+
stale: 10000 // 10 seconds
|
|
38
|
+
});
|
|
39
|
+
try {
|
|
40
|
+
output = await fn();
|
|
41
|
+
} finally {
|
|
42
|
+
await release();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return output;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Write PID to file
|
|
50
|
+
* @param {number} pid - Process ID to write
|
|
51
|
+
*/
|
|
52
|
+
const writePidFile = async pid => {
|
|
53
|
+
try {
|
|
54
|
+
await fs.promises.writeFile(PID_FILE, pid.toString(), 'utf8');
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error.code === 'ENOENT') {
|
|
57
|
+
// File doesn't exist, create it without locking
|
|
58
|
+
await fs.promises.writeFile(PID_FILE, pid.toString(), 'utf8');
|
|
59
|
+
} else {
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Read PID from file
|
|
67
|
+
* @returns {number|null} PID if found and valid, null otherwise
|
|
68
|
+
*/
|
|
69
|
+
const readPidFile = async () => {
|
|
70
|
+
try {
|
|
71
|
+
const pidStr = await fs.promises.readFile(PID_FILE, 'utf8');
|
|
72
|
+
const pid = parseInt(pidStr.trim(), 10);
|
|
73
|
+
|
|
74
|
+
if (isNaN(pid) || pid <= 0) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return pid;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error.code === 'ENOENT') {
|
|
81
|
+
return null; // File doesn't exist
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Remove PID file
|
|
89
|
+
*/
|
|
90
|
+
const removePidFileWithLock = async () => {
|
|
91
|
+
try {
|
|
92
|
+
const release = await lockfile.lock(PID_FILE, {
|
|
93
|
+
retries: 3,
|
|
94
|
+
stale: 10000 // 10 seconds
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await removePidFile();
|
|
99
|
+
} finally {
|
|
100
|
+
await release();
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error.code === 'ENOENT') {
|
|
104
|
+
// File already doesn't exist, that's fine
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const removePidFile = async () => {
|
|
112
|
+
const pid = await readPidFile();
|
|
113
|
+
if (pid === process.pid) {
|
|
114
|
+
await fs.promises.unlink(PID_FILE);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check if a process with given PID exists and is a caffeine server
|
|
120
|
+
* @param {number} pid - Process ID to check
|
|
121
|
+
* @returns {Promise<boolean>} True if process exists and is caffeine server
|
|
122
|
+
*/
|
|
123
|
+
const validatePid = async pid => {
|
|
124
|
+
return new Promise(resolve => {
|
|
125
|
+
// First check if process exists
|
|
126
|
+
try {
|
|
127
|
+
process.kill(pid, 0); // Signal 0 just checks if process exists
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (error.code === 'ESRCH') {
|
|
130
|
+
// Process doesn't exist
|
|
131
|
+
resolve(false);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Other errors (like EPERM) mean process exists but we can't signal it
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Process exists, now check if it's a caffeine server
|
|
138
|
+
const isWindows = os.platform() === 'win32';
|
|
139
|
+
const psCommand = isWindows
|
|
140
|
+
? spawn('wmic', ['process', 'where', `processid=${pid}`, 'get', 'commandline'], {
|
|
141
|
+
stdio: 'pipe'
|
|
142
|
+
})
|
|
143
|
+
: spawn('ps', ['-p', pid, '-o', 'command='], { stdio: 'pipe' });
|
|
144
|
+
|
|
145
|
+
let output = '';
|
|
146
|
+
|
|
147
|
+
psCommand.stdout.on('data', data => {
|
|
148
|
+
output += data.toString();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
psCommand.on('close', code => {
|
|
152
|
+
if (code !== 0) {
|
|
153
|
+
resolve(false);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const commandLine = output.trim().toLowerCase();
|
|
158
|
+
for (const line of commandLine.split('\n')) {
|
|
159
|
+
// Check if command line contains both "caffeine" and "server"
|
|
160
|
+
const isCaffeineServer = line.includes('caffeine server') || line.includes('caffeine.js server');
|
|
161
|
+
const isElectron = line.includes('electron');
|
|
162
|
+
|
|
163
|
+
if (isCaffeineServer && isElectron) {
|
|
164
|
+
resolve(true);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
resolve(false);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
psCommand.on('error', () => {
|
|
173
|
+
resolve(false);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if caffeine server is running using PID file
|
|
180
|
+
* @returns {Promise<boolean>} True if server is running
|
|
181
|
+
*/
|
|
182
|
+
const isServerRunningWithLock = async () => {
|
|
183
|
+
return await withPidLock(async () => {
|
|
184
|
+
return await isServerRunning();
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if caffeine server is running using PID file
|
|
190
|
+
* @returns {Promise<boolean>} True if server is running
|
|
191
|
+
*/
|
|
192
|
+
const isServerRunning = async () => {
|
|
193
|
+
try {
|
|
194
|
+
const pid = await readPidFile();
|
|
195
|
+
|
|
196
|
+
if (!pid) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const isValid = await validatePid(pid);
|
|
201
|
+
|
|
202
|
+
if (!isValid) {
|
|
203
|
+
// PID is stale, clean it up
|
|
204
|
+
await removePidFile();
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return true;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error('Error checking if server is running:', error);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
module.exports = {
|
|
216
|
+
writePidFile,
|
|
217
|
+
readPidFile,
|
|
218
|
+
removePidFileWithLock,
|
|
219
|
+
removePidFile,
|
|
220
|
+
validatePid,
|
|
221
|
+
isServerRunningWithLock,
|
|
222
|
+
isServerRunning,
|
|
223
|
+
withPidLock
|
|
224
|
+
};
|
package/src/server.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server module - Handles server process management and startup
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const { initSessionsFile } = require('./session');
|
|
9
|
+
const { getSystemTray, startPolling, shutdownServer } = require('./system-tray');
|
|
10
|
+
const {
|
|
11
|
+
isRunningInElectron,
|
|
12
|
+
preventWindowCreation,
|
|
13
|
+
setupAppEventHandlers,
|
|
14
|
+
whenReady,
|
|
15
|
+
quit
|
|
16
|
+
} = require('./electron');
|
|
17
|
+
const { isServerRunning, writePidFile, withPidLock, isServerRunningWithLock } = require('./pid');
|
|
18
|
+
|
|
19
|
+
const CHECK_INTERVAL = 5 * 1000; // 10 seconds
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Ensure server is running, start if needed
|
|
23
|
+
*/
|
|
24
|
+
const runServerProcessIfNotStarted = async () => {
|
|
25
|
+
const isRunning = await isServerRunningWithLock();
|
|
26
|
+
if (isRunning) {
|
|
27
|
+
console.error('Server is already running');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.error('Server not running, starting...');
|
|
32
|
+
await startServerProcess();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Start server process using npm
|
|
37
|
+
*/
|
|
38
|
+
const startServerProcess = async () => {
|
|
39
|
+
console.error('Starting caffeine server...');
|
|
40
|
+
|
|
41
|
+
const cwd = path.join(__dirname, '..');
|
|
42
|
+
|
|
43
|
+
const serverProcess = spawn('npm', ['run', 'server'], {
|
|
44
|
+
detached: true,
|
|
45
|
+
stdio: 'ignore',
|
|
46
|
+
cwd // is needed to find the correct caffeine.js
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
serverProcess.unref();
|
|
50
|
+
|
|
51
|
+
// Wait for server to start
|
|
52
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
53
|
+
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Handle server command - start Electron server or delegate with atomic file locking
|
|
59
|
+
*/
|
|
60
|
+
const handleServer = async () => {
|
|
61
|
+
let mustStartServer = false;
|
|
62
|
+
let mustStartElectron = false;
|
|
63
|
+
|
|
64
|
+
await withPidLock(async () => {
|
|
65
|
+
try {
|
|
66
|
+
// Inside the lock, check if server is already running
|
|
67
|
+
const alreadyRunning = await isServerRunning();
|
|
68
|
+
if (alreadyRunning) {
|
|
69
|
+
console.error('Caffeine server is already running');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isRunningInElectron()) {
|
|
74
|
+
mustStartServer = true;
|
|
75
|
+
console.error('Already running inside Electron, starting server...');
|
|
76
|
+
await writePidFile(process.pid);
|
|
77
|
+
} else {
|
|
78
|
+
mustStartElectron = true;
|
|
79
|
+
console.error('Not running inside Electron, spawning Electron process...');
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error.code === 'ELOCKED' || error.code === 'EEXIST') {
|
|
83
|
+
// Another process has the lock, server is likely starting up
|
|
84
|
+
console.error('Server startup is in progress by another process');
|
|
85
|
+
} else {
|
|
86
|
+
console.error('Failed to acquire server startup lock:', error);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (mustStartElectron) {
|
|
93
|
+
await spawnElectronProcess();
|
|
94
|
+
} else if (mustStartServer) {
|
|
95
|
+
await startServer();
|
|
96
|
+
} else if (isRunningInElectron()) {
|
|
97
|
+
await shutdownServer();
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Start server when already inside Electron
|
|
104
|
+
*/
|
|
105
|
+
const startServer = async () => {
|
|
106
|
+
console.error('Loading Electron...');
|
|
107
|
+
|
|
108
|
+
// Prevent any window from being created
|
|
109
|
+
preventWindowCreation();
|
|
110
|
+
|
|
111
|
+
// Setup event handlers with shutdown callback
|
|
112
|
+
setupAppEventHandlers(() => {
|
|
113
|
+
// We'll handle shutdown in the main process
|
|
114
|
+
process.exit(0);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Wait for app to be ready before starting system tray
|
|
118
|
+
await whenReady();
|
|
119
|
+
|
|
120
|
+
// Start the actual server
|
|
121
|
+
try {
|
|
122
|
+
await initSessionsFile();
|
|
123
|
+
const state = getSystemTray();
|
|
124
|
+
startPolling(state, CHECK_INTERVAL);
|
|
125
|
+
console.error('Caffeine server started successfully with system tray');
|
|
126
|
+
|
|
127
|
+
// Only setup signal handlers if server actually started
|
|
128
|
+
if (state) {
|
|
129
|
+
// Handle process termination for Electron process
|
|
130
|
+
process.on('SIGINT', async () => {
|
|
131
|
+
console.error('Received SIGINT, shutting down server...');
|
|
132
|
+
await shutdownServer(state);
|
|
133
|
+
quit();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
process.on('SIGTERM', async () => {
|
|
137
|
+
console.error('Received SIGTERM, shutting down server...');
|
|
138
|
+
await shutdownServer(state);
|
|
139
|
+
quit();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return state;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error('Failed to start server:', error);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Spawn new Electron process for server
|
|
152
|
+
*/
|
|
153
|
+
const spawnElectronProcess = () => {
|
|
154
|
+
const cwd = path.join(__dirname, '..');
|
|
155
|
+
|
|
156
|
+
const electronProcess = spawn('npx', ['electron', 'caffeine.js', 'server'], {
|
|
157
|
+
stdio: 'inherit',
|
|
158
|
+
shell: true,
|
|
159
|
+
detached: false,
|
|
160
|
+
cwd // is needed to find caffeine.js
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
electronProcess.on('exit', code => {
|
|
164
|
+
process.exit(code || 0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
electronProcess.on('error', error => {
|
|
168
|
+
console.error('Failed to spawn Electron process:', error);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
electronProcess.on('close', code => {
|
|
173
|
+
process.exit(code || 0);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return electronProcess.pid;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
module.exports = {
|
|
180
|
+
handleServer,
|
|
181
|
+
runServerProcessIfNotStarted
|
|
182
|
+
};
|