go-dev 0.4.1 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "go-dev",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "main": "src/index.js",
5
5
  "bin": {
6
6
  "go-dev": "bin/go-dev"
@@ -1,6 +1,8 @@
1
- const { spawn, spawnSync } = require('child_process');
1
+ const { spawn, spawnSync, execSync } = require('child_process');
2
2
  const prefixLines = require("./prefix-lines");
3
3
 
4
+ const isWindows = process.platform === 'win32';
5
+
4
6
  class ProcessManager {
5
7
  constructor() {
6
8
  this.managedProcesses = new Set();
@@ -123,13 +125,11 @@ class ProcessManager {
123
125
  startedProcess.stdout.on('data', (data) => {
124
126
  const result = prefixLines(data.toString(), prefix, lastFormatting);
125
127
  lastFormatting = result.lastFormatting;
126
- // process.stdout.write(JSON.stringify(data.toString()) + "\n");
127
128
  process.stdout.write(result.prefixedText);
128
129
  });
129
130
  startedProcess.stderr.on('data', (data) => {
130
131
  const result = prefixLines(data.toString(), prefix, lastFormatting);
131
132
  lastFormatting = result.lastFormatting;
132
- // process.stderr.write(JSON.stringify(data.toString()) + "\n");
133
133
  process.stderr.write(result.prefixedText);
134
134
  });
135
135
 
@@ -178,47 +178,65 @@ class ProcessManager {
178
178
  return processReference;
179
179
  }
180
180
 
181
- killProcess(process) {
182
- if (process.killed || process.exitCode != null) {
183
- return;
181
+ /**
182
+ * Kills a single child process.
183
+ * On Windows, uses taskkill to kill the entire process tree.
184
+ * @param {ChildProcess} childProcess - The process to kill.
185
+ * @returns {Promise<void>}
186
+ */
187
+ killProcess(childProcess) {
188
+ if (childProcess.killed || childProcess.exitCode != null) {
189
+ return Promise.resolve();
184
190
  }
185
191
 
186
- this.managedProcesses.delete(process);
192
+ this.managedProcesses.delete(childProcess);
187
193
 
188
- return new Promise((resolve, reject) => {
194
+ if (isWindows) {
195
+ try {
196
+ execSync(`taskkill /T /F /PID ${childProcess.pid}`, { stdio: 'ignore' });
197
+ } catch {
198
+ // Process may have already been terminated by Ctrl+C signal propagation - this is OK
199
+ }
200
+ return Promise.resolve();
201
+ }
202
+
203
+ // On Unix, use SIGTERM with timeout fallback to SIGKILL
204
+ return new Promise((resolve) => {
189
205
  let exited = false;
206
+ let timeoutId = null;
207
+
190
208
  const onExit = () => {
191
209
  exited = true;
210
+ if (timeoutId) {
211
+ clearTimeout(timeoutId);
212
+ timeoutId = null;
213
+ }
192
214
  resolve();
193
215
  };
194
- process.on('exit', onExit);
216
+ childProcess.on('exit', onExit);
195
217
 
196
- setTimeout(() => {
218
+ timeoutId = setTimeout(() => {
197
219
  if (exited) {
198
220
  return;
199
221
  }
200
222
 
201
- console.error(`[ProcessManager] Timeout reached for process interruption ${process.pid}`);
223
+ console.error(`[ProcessManager] Timeout reached for process interruption ${childProcess.pid}`);
202
224
  try {
203
- process.kill('SIGKILL');
225
+ childProcess.kill('SIGKILL');
204
226
  } catch (e) {
205
- console.error(`[ProcessManager] Error force killing process ${process.pid}: ${e.message}`);
206
- reject(e);
227
+ console.error(`[ProcessManager] Error force killing process ${childProcess.pid}: ${e.message}`);
207
228
  }
229
+ resolve();
208
230
  }, 500);
209
231
 
210
232
  try {
211
- process.kill('SIGTERM');
212
- return;
213
- } catch (e) {
214
- console.error(`[ProcessManager] Error signaling process ${process.pid}: ${e.message}`);
215
- }
216
-
217
- try {
218
- process.kill('SIGKILL');
233
+ childProcess.kill('SIGTERM');
219
234
  } catch (e) {
220
- console.error(`[ProcessManager] Error force killing process ${process.pid}: ${e.message}`);
221
- reject(e);
235
+ console.error(`[ProcessManager] Error signaling process ${childProcess.pid}: ${e.message}`);
236
+ if (timeoutId) {
237
+ clearTimeout(timeoutId);
238
+ }
239
+ resolve();
222
240
  }
223
241
  });
224
242
  }
@@ -231,41 +249,54 @@ class ProcessManager {
231
249
  console.log('[ProcessManager] Cleanup of managed processes already in progress, skipping.');
232
250
  return;
233
251
  }
234
- for (let index = this.managedProcesses.length - 1; index >= 0; index--) {
235
- const process = this.managedProcesses[index];
236
- if (process.killed || process.exitCode != null) {
237
- this.managedProcesses.splice(index, 1);
238
- }
239
- }
240
252
 
241
253
  this.cleanupInProgress = true;
242
254
 
243
255
  console.log('\n[ProcessManager] Initiating cleanup of managed processes...');
244
256
 
245
- for (const proc of [...this.managedProcesses]) {
257
+ // Filter out already-dead processes
258
+ const processesToKill = [...this.managedProcesses].filter(proc => {
246
259
  if (proc.killed || proc.exitCode != null) {
247
- continue;
260
+ this.managedProcesses.delete(proc);
261
+ return false;
248
262
  }
249
-
263
+ return true;
264
+ });
265
+
266
+ for (const proc of processesToKill) {
250
267
  console.log(`[ProcessManager] Killing managed process PID: ${proc.pid}`);
251
268
  try {
252
- proc.kill('SIGTERM');
269
+ if (isWindows) {
270
+ // On Windows, use taskkill to kill the entire process tree
271
+ try {
272
+ execSync(`taskkill /T /F /PID ${proc.pid}`, { stdio: 'ignore' });
273
+ } catch {
274
+ // Process may have already been terminated - this is OK
275
+ }
276
+ } else {
277
+ proc.kill('SIGTERM');
278
+ }
253
279
  } catch (e) {
254
280
  console.error(`[ProcessManager] Error killing process ${proc.pid}: ${e.message}`);
255
281
  }
256
282
  }
257
- await new Promise(resolve => setTimeout(resolve, 500));
258
-
259
- for (const proc of [...this.managedProcesses]) {
260
- if (proc.killed || proc.exitCode != null) {
261
- continue;
262
- }
283
+
284
+ // On Unix, wait for processes to exit gracefully before using SIGKILL
285
+ if (processesToKill.length > 0 && !isWindows) {
286
+ await new Promise(resolve => setTimeout(resolve, 500));
287
+
288
+ for (const proc of [...this.managedProcesses]) {
289
+ if (proc.killed || proc.exitCode != null) {
290
+ this.managedProcesses.delete(proc);
291
+ continue;
292
+ }
263
293
 
264
- console.warn(`[ProcessManager] Process ${proc.pid} did not exit gracefully, forcing kill.`);
265
- try {
266
- proc.kill('SIGKILL');
267
- } catch (e) {
268
- console.error(`[ProcessManager] Error forcing kill for process ${proc.pid}: ${e.message}`);
294
+ console.warn(`[ProcessManager] Process ${proc.pid} did not exit gracefully, forcing kill.`);
295
+ try {
296
+ proc.kill('SIGKILL');
297
+ } catch (e) {
298
+ console.error(`[ProcessManager] Error forcing kill for process ${proc.pid}: ${e.message}`);
299
+ }
269
300
  }
270
301
  }
271
302