prjct-cli 1.6.9 → 1.6.10
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/CHANGELOG.md +33 -1
- package/core/bus/bus.ts +24 -0
- package/core/services/watch-service.ts +20 -3
- package/dist/bin/prjct.mjs +18 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.10] - 2026-02-07
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- resolve signal handler and EventBus listener accumulation leaks (PRJ-287) (#135)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [1.6.12] - 2026-02-07
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
- **Fix signal handler and EventBus listener accumulation leaks (PRJ-287)**: WatchService signal handlers (`SIGINT`/`SIGTERM`) are now stored by reference and removed in `stop()`, preventing accumulation on restart cycles. `pendingChanges` Set is cleared on stop. EventBus gains `flush()` to clear history and stale once-listeners, and `removeAllListeners(event?)` for targeted cleanup.
|
|
14
|
+
|
|
15
|
+
### Implementation Details
|
|
16
|
+
Stored signal handler references as class properties (`sigintHandler`, `sigtermHandler`). In `start()`, old handlers are removed before new ones are added. In `stop()`, handlers are removed via `process.off()` and `pendingChanges` is cleared. EventBus `flush()` clears history array and all once-listeners. `removeAllListeners()` supports both targeted (single event) and global cleanup.
|
|
17
|
+
|
|
18
|
+
### Learnings
|
|
19
|
+
- Arrow functions passed to `process.on()` cannot be removed — must store named handler references for `process.off()`.
|
|
20
|
+
- Cleanup code after `process.exit(0)` is unreachable — perform all cleanup before the exit call.
|
|
21
|
+
|
|
22
|
+
### Test Plan
|
|
23
|
+
|
|
24
|
+
#### For QA
|
|
25
|
+
1. Start/stop watch mode 10 times — verify only 2 signal handlers (not 20)
|
|
26
|
+
2. Trigger file changes, stop — verify `pendingChanges` cleared
|
|
27
|
+
3. Emit 50 events, call `flush()` — verify history empty
|
|
28
|
+
4. Register `once()` for unfired event, `flush()` — verify listener removed
|
|
29
|
+
5. `removeAllListeners('event')` — verify only that event cleared
|
|
30
|
+
6. `removeAllListeners()` — verify all cleared
|
|
31
|
+
|
|
32
|
+
#### For Users
|
|
33
|
+
**What changed:** WatchService no longer leaks signal handlers on restart. EventBus has `flush()` and `removeAllListeners()`.
|
|
34
|
+
**Breaking changes:** None.
|
|
35
|
+
|
|
3
36
|
## [1.6.9] - 2026-02-07
|
|
4
37
|
|
|
5
38
|
### Bug Fixes
|
|
6
39
|
|
|
7
40
|
- resolve SSE zombie connections and infinite promise leak (PRJ-286) (#134)
|
|
8
41
|
|
|
9
|
-
|
|
10
42
|
## [1.6.11] - 2026-02-07
|
|
11
43
|
|
|
12
44
|
### Bug Fixes
|
package/core/bus/bus.ts
CHANGED
|
@@ -237,6 +237,30 @@ class EventBus {
|
|
|
237
237
|
this.onceListeners.clear()
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Flush event history and clean up stale once-listeners.
|
|
242
|
+
* Call on task completion, project switch, or periodically.
|
|
243
|
+
*/
|
|
244
|
+
flush(): void {
|
|
245
|
+
this.history = []
|
|
246
|
+
|
|
247
|
+
// Remove once-listeners for events that were never fired
|
|
248
|
+
this.onceListeners.clear()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Remove all listeners for a specific event, or all events if none specified.
|
|
253
|
+
*/
|
|
254
|
+
removeAllListeners(event?: string): void {
|
|
255
|
+
if (event) {
|
|
256
|
+
this.listeners.delete(event)
|
|
257
|
+
this.onceListeners.delete(event)
|
|
258
|
+
} else {
|
|
259
|
+
this.listeners.clear()
|
|
260
|
+
this.onceListeners.clear()
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
240
264
|
/**
|
|
241
265
|
* Get count of listeners for an event
|
|
242
266
|
*/
|
|
@@ -102,6 +102,8 @@ class WatchService {
|
|
|
102
102
|
}
|
|
103
103
|
private isRunning: boolean = false
|
|
104
104
|
private syncCount: number = 0
|
|
105
|
+
private sigintHandler: (() => void) | null = null
|
|
106
|
+
private sigtermHandler: (() => void) | null = null
|
|
105
107
|
|
|
106
108
|
/**
|
|
107
109
|
* Start watching for file changes
|
|
@@ -151,9 +153,13 @@ class WatchService {
|
|
|
151
153
|
.on('unlink', (filePath: string) => this.handleChange('unlink', filePath))
|
|
152
154
|
.on('error', (error: unknown) => this.handleError(error as Error))
|
|
153
155
|
|
|
154
|
-
// Handle graceful shutdown
|
|
155
|
-
process.
|
|
156
|
-
process.
|
|
156
|
+
// Handle graceful shutdown — store references for proper removal
|
|
157
|
+
if (this.sigintHandler) process.off('SIGINT', this.sigintHandler)
|
|
158
|
+
if (this.sigtermHandler) process.off('SIGTERM', this.sigtermHandler)
|
|
159
|
+
this.sigintHandler = () => this.stop()
|
|
160
|
+
this.sigtermHandler = () => this.stop()
|
|
161
|
+
process.on('SIGINT', this.sigintHandler)
|
|
162
|
+
process.on('SIGTERM', this.sigtermHandler)
|
|
157
163
|
|
|
158
164
|
return { success: true }
|
|
159
165
|
}
|
|
@@ -177,6 +183,17 @@ class WatchService {
|
|
|
177
183
|
this.watcher = null
|
|
178
184
|
}
|
|
179
185
|
|
|
186
|
+
// Remove signal handlers to prevent accumulation
|
|
187
|
+
if (this.sigintHandler) {
|
|
188
|
+
process.off('SIGINT', this.sigintHandler)
|
|
189
|
+
this.sigintHandler = null
|
|
190
|
+
}
|
|
191
|
+
if (this.sigtermHandler) {
|
|
192
|
+
process.off('SIGTERM', this.sigtermHandler)
|
|
193
|
+
this.sigtermHandler = null
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.pendingChanges.clear()
|
|
180
197
|
this.isRunning = false
|
|
181
198
|
process.exit(0)
|
|
182
199
|
}
|
package/dist/bin/prjct.mjs
CHANGED
|
@@ -23753,6 +23753,8 @@ var init_watch_service = __esm({
|
|
|
23753
23753
|
};
|
|
23754
23754
|
isRunning = false;
|
|
23755
23755
|
syncCount = 0;
|
|
23756
|
+
sigintHandler = null;
|
|
23757
|
+
sigtermHandler = null;
|
|
23756
23758
|
/**
|
|
23757
23759
|
* Start watching for file changes
|
|
23758
23760
|
*/
|
|
@@ -23783,8 +23785,12 @@ var init_watch_service = __esm({
|
|
|
23783
23785
|
}
|
|
23784
23786
|
});
|
|
23785
23787
|
this.watcher.on("add", (filePath) => this.handleChange("add", filePath)).on("change", (filePath) => this.handleChange("change", filePath)).on("unlink", (filePath) => this.handleChange("unlink", filePath)).on("error", (error) => this.handleError(error));
|
|
23786
|
-
process.
|
|
23787
|
-
process.
|
|
23788
|
+
if (this.sigintHandler) process.off("SIGINT", this.sigintHandler);
|
|
23789
|
+
if (this.sigtermHandler) process.off("SIGTERM", this.sigtermHandler);
|
|
23790
|
+
this.sigintHandler = () => this.stop();
|
|
23791
|
+
this.sigtermHandler = () => this.stop();
|
|
23792
|
+
process.on("SIGINT", this.sigintHandler);
|
|
23793
|
+
process.on("SIGTERM", this.sigtermHandler);
|
|
23788
23794
|
return { success: true };
|
|
23789
23795
|
}
|
|
23790
23796
|
/**
|
|
@@ -23804,6 +23810,15 @@ var init_watch_service = __esm({
|
|
|
23804
23810
|
await this.watcher.close();
|
|
23805
23811
|
this.watcher = null;
|
|
23806
23812
|
}
|
|
23813
|
+
if (this.sigintHandler) {
|
|
23814
|
+
process.off("SIGINT", this.sigintHandler);
|
|
23815
|
+
this.sigintHandler = null;
|
|
23816
|
+
}
|
|
23817
|
+
if (this.sigtermHandler) {
|
|
23818
|
+
process.off("SIGTERM", this.sigtermHandler);
|
|
23819
|
+
this.sigtermHandler = null;
|
|
23820
|
+
}
|
|
23821
|
+
this.pendingChanges.clear();
|
|
23807
23822
|
this.isRunning = false;
|
|
23808
23823
|
process.exit(0);
|
|
23809
23824
|
}
|
|
@@ -28649,7 +28664,7 @@ var require_package = __commonJS({
|
|
|
28649
28664
|
"package.json"(exports, module) {
|
|
28650
28665
|
module.exports = {
|
|
28651
28666
|
name: "prjct-cli",
|
|
28652
|
-
version: "1.6.
|
|
28667
|
+
version: "1.6.10",
|
|
28653
28668
|
description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
|
|
28654
28669
|
main: "core/index.ts",
|
|
28655
28670
|
bin: {
|