@pokit/tabs-core 0.0.39 → 0.0.42

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.
@@ -43,6 +43,7 @@ export declare class ProcessManager {
43
43
  private options;
44
44
  private processes;
45
45
  private outputBuffers;
46
+ private expectedStops;
46
47
  private flushScheduled;
47
48
  private destroyed;
48
49
  constructor(items: TabSpec[], options: ProcessManagerOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../src/process-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExD,eAAO,MAAM,eAAe,KAAK,CAAC;AAMlC;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,2DAA2D;IAC3D,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACzD,2CAA2C;IAC3C,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9E,gDAAgD;IAChD,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,kCAAkC;IAClC,SAAS,EAAE,uBAAuB,CAAC;CACpC,CAAC;AAUF;;;;;;;GAOG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,aAAa,CAAwC;IAC7D,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,SAAS,CAAS;gBAEd,KAAK,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,qBAAqB;IAM5D;;OAEG;IACH,cAAc,IAAI,UAAU,EAAE;IAU9B;;OAEG;IACH,KAAK,IAAI,IAAI;IAQb;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IA2B5B;;OAEG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAUzB;;OAEG;IACH,OAAO,IAAI,IAAI;IAQf;;OAEG;IACH,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,YAAY;IAgCpB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,WAAW;CAcpB"}
1
+ {"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../src/process-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAWxD,eAAO,MAAM,eAAe,KAAK,CAAC;AAMlC;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,2DAA2D;IAC3D,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACzD,2CAA2C;IAC3C,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9E,gDAAgD;IAChD,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,kCAAkC;IAClC,SAAS,EAAE,uBAAuB,CAAC;CACpC,CAAC;AAUF;;;;;;;GAOG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,aAAa,CAAwC;IAC7D,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,SAAS,CAAS;gBAEd,KAAK,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,qBAAqB;IAM5D;;OAEG;IACH,cAAc,IAAI,UAAU,EAAE;IAU9B;;OAEG;IACH,KAAK,IAAI,IAAI;IAQb;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IA4B5B;;OAEG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAWzB;;OAEG;IACH,OAAO,IAAI,IAAI;IASf;;OAEG;IACH,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,YAAY;IAwCpB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,WAAW;CAcpB"}
@@ -5,6 +5,16 @@
5
5
  * Framework-agnostic - provides callbacks for UI frameworks to consume.
6
6
  */
7
7
  import { spawn } from 'node:child_process';
8
+ function killProcessTree(proc) {
9
+ if (proc.killed || proc.pid == null)
10
+ return;
11
+ try {
12
+ process.kill(-proc.pid, 'SIGTERM');
13
+ }
14
+ catch {
15
+ // Process may have already exited
16
+ }
17
+ }
8
18
  export const OUTPUT_BATCH_MS = 16;
9
19
  // =============================================================================
10
20
  // ProcessManager Class
@@ -22,6 +32,7 @@ export class ProcessManager {
22
32
  options;
23
33
  processes = [];
24
34
  outputBuffers = new Map();
35
+ expectedStops = new Set();
25
36
  flushScheduled = false;
26
37
  destroyed = false;
27
38
  constructor(items, options) {
@@ -62,8 +73,9 @@ export class ProcessManager {
62
73
  return;
63
74
  // Kill existing process
64
75
  const existingProc = this.processes[index];
65
- if (existingProc && !existingProc.killed) {
66
- existingProc.kill('SIGTERM');
76
+ if (existingProc) {
77
+ this.expectedStops.add(index);
78
+ killProcessTree(existingProc);
67
79
  }
68
80
  // Clear output buffer
69
81
  this.outputBuffers.delete(index);
@@ -82,8 +94,9 @@ export class ProcessManager {
82
94
  */
83
95
  kill(index) {
84
96
  const proc = this.processes[index];
85
- if (proc && !proc.killed) {
86
- proc.kill('SIGTERM');
97
+ if (proc) {
98
+ this.expectedStops.add(index);
99
+ killProcessTree(proc);
87
100
  }
88
101
  this.options.callbacks.onOutputUpdate(index, ['', 'Stopped']);
89
102
  this.options.callbacks.onStatusChange(index, 'stopped');
@@ -92,9 +105,10 @@ export class ProcessManager {
92
105
  * Kill all processes
93
106
  */
94
107
  killAll() {
95
- for (const proc of this.processes) {
96
- if (proc && !proc.killed) {
97
- proc.kill('SIGTERM');
108
+ for (const [index, proc] of this.processes.entries()) {
109
+ if (proc) {
110
+ this.expectedStops.add(index);
111
+ killProcessTree(proc);
98
112
  }
99
113
  }
100
114
  }
@@ -120,6 +134,7 @@ export class ProcessManager {
120
134
  FORCE_COLOR: '1',
121
135
  },
122
136
  stdio: ['inherit', 'pipe', 'pipe'],
137
+ detached: true,
123
138
  });
124
139
  this.processes[index] = proc;
125
140
  const handleData = (data) => this.appendOutput(index, data);
@@ -127,6 +142,11 @@ export class ProcessManager {
127
142
  proc.stderr?.on('data', handleData);
128
143
  proc.on('close', (code) => {
129
144
  this.flushOutput();
145
+ this.processes[index] = null;
146
+ // Ignore close transitions for intentional stops (kill/restart/destroy).
147
+ if (this.expectedStops.delete(index)) {
148
+ return;
149
+ }
130
150
  const status = code === 0 ? 'done' : 'error';
131
151
  this.options.callbacks.onStatusChange(index, status, code ?? undefined);
132
152
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pokit/tabs-core",
3
- "version": "0.0.39",
3
+ "version": "0.0.42",
4
4
  "description": "Core tab management utilities for pok CLI applications",
5
5
  "keywords": [
6
6
  "cli",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "react": "^18.0.0 || ^19.0.0",
48
- "@pokit/core": "0.0.39"
48
+ "@pokit/core": "0.0.42"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "react": {
@@ -56,7 +56,7 @@
56
56
  "@types/bun": "latest",
57
57
  "@types/react": "^19.2.0",
58
58
  "react": "^19.2.0",
59
- "@pokit/core": "0.0.39"
59
+ "@pokit/core": "0.0.42"
60
60
  },
61
61
  "engines": {
62
62
  "bun": ">=1.0.0"
@@ -8,6 +8,15 @@
8
8
  import { spawn, type ChildProcess } from 'node:child_process';
9
9
  import type { TabStatus, TabProcess } from './types.js';
10
10
 
11
+ function killProcessTree(proc: ChildProcess): void {
12
+ if (proc.killed || proc.pid == null) return;
13
+ try {
14
+ process.kill(-proc.pid, 'SIGTERM');
15
+ } catch {
16
+ // Process may have already exited
17
+ }
18
+ }
19
+
11
20
  export const OUTPUT_BATCH_MS = 16;
12
21
 
13
22
  // =============================================================================
@@ -62,6 +71,7 @@ export class ProcessManager {
62
71
  private options: ProcessManagerOptions;
63
72
  private processes: (ChildProcess | null)[] = [];
64
73
  private outputBuffers: Map<number, OutputBuffer> = new Map();
74
+ private expectedStops = new Set<number>();
65
75
  private flushScheduled = false;
66
76
  private destroyed = false;
67
77
 
@@ -106,8 +116,9 @@ export class ProcessManager {
106
116
 
107
117
  // Kill existing process
108
118
  const existingProc = this.processes[index];
109
- if (existingProc && !existingProc.killed) {
110
- existingProc.kill('SIGTERM');
119
+ if (existingProc) {
120
+ this.expectedStops.add(index);
121
+ killProcessTree(existingProc);
111
122
  }
112
123
 
113
124
  // Clear output buffer
@@ -130,8 +141,9 @@ export class ProcessManager {
130
141
  */
131
142
  kill(index: number): void {
132
143
  const proc = this.processes[index];
133
- if (proc && !proc.killed) {
134
- proc.kill('SIGTERM');
144
+ if (proc) {
145
+ this.expectedStops.add(index);
146
+ killProcessTree(proc);
135
147
  }
136
148
 
137
149
  this.options.callbacks.onOutputUpdate(index, ['', 'Stopped']);
@@ -142,9 +154,10 @@ export class ProcessManager {
142
154
  * Kill all processes
143
155
  */
144
156
  killAll(): void {
145
- for (const proc of this.processes) {
146
- if (proc && !proc.killed) {
147
- proc.kill('SIGTERM');
157
+ for (const [index, proc] of this.processes.entries()) {
158
+ if (proc) {
159
+ this.expectedStops.add(index);
160
+ killProcessTree(proc);
148
161
  }
149
162
  }
150
163
  }
@@ -173,6 +186,7 @@ export class ProcessManager {
173
186
  FORCE_COLOR: '1',
174
187
  } as NodeJS.ProcessEnv,
175
188
  stdio: ['inherit', 'pipe', 'pipe'],
189
+ detached: true,
176
190
  });
177
191
 
178
192
  this.processes[index] = proc;
@@ -184,6 +198,13 @@ export class ProcessManager {
184
198
 
185
199
  proc.on('close', (code) => {
186
200
  this.flushOutput();
201
+ this.processes[index] = null;
202
+
203
+ // Ignore close transitions for intentional stops (kill/restart/destroy).
204
+ if (this.expectedStops.delete(index)) {
205
+ return;
206
+ }
207
+
187
208
  const status: TabStatus = code === 0 ? 'done' : 'error';
188
209
  this.options.callbacks.onStatusChange(index, status, code ?? undefined);
189
210
  });