@hienlh/ppm 0.4.4 → 0.4.5

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 CHANGED
@@ -1,10 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## [0.4.4] - 2026-03-17
3
+ ## [0.4.5] - 2026-03-17
4
4
 
5
5
  ### Fixed
6
+ - Windows: graceful shutdown — Ctrl+C kills cloudflared + releases port
7
+ - Windows: `ppm stop` kills orphan cloudflared.exe via taskkill
6
8
  - Windows: cloudflared tunnel uses 127.0.0.1 instead of localhost (IPv6 mismatch)
7
- - npm package size reduced from 79MB to ~580KB (exclude compiled binary + leaked Monaco workers)
9
+ - npm package size reduced from 79MB to ~580KB
8
10
 
9
11
  ## [0.4.2] - 2026-03-17
10
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -45,6 +45,14 @@ export async function stopServer() {
45
45
  // Kill tunnel process (independent from server)
46
46
  if (tunnelPid) killPid(tunnelPid, "tunnel");
47
47
 
48
+ // Windows fallback: kill orphan cloudflared processes spawned by PPM
49
+ if (process.platform === "win32") {
50
+ try {
51
+ const cfPath = resolve(homedir(), ".ppm", "bin", "cloudflared.exe");
52
+ Bun.spawnSync(["taskkill", "/F", "/IM", "cloudflared.exe"], { stdout: "ignore", stderr: "ignore" });
53
+ } catch {}
54
+ }
55
+
48
56
  cleanup();
49
57
  console.log("PPM stopped.");
50
58
  }
@@ -415,6 +415,18 @@ export async function startServer(options: {
415
415
  console.log(` Token: ${configService.get("auth").token}`);
416
416
  }
417
417
  console.log();
418
+
419
+ // Graceful shutdown — stop server + tunnel on exit (especially important on Windows)
420
+ const shutdown = () => {
421
+ try { server.stop(true); } catch {}
422
+ try {
423
+ // Dynamic import to avoid circular — tunnel may not be loaded
424
+ import("../services/tunnel.service.ts").then(({ tunnelService }) => tunnelService.stopTunnel()).catch(() => {});
425
+ } catch {}
426
+ };
427
+ process.on("SIGINT", () => { shutdown(); process.exit(0); });
428
+ process.on("SIGTERM", () => { shutdown(); process.exit(0); });
429
+ process.on("exit", shutdown);
418
430
  }
419
431
 
420
432
  // Internal entry point for daemon child process
@@ -29,10 +29,13 @@ class TunnelService {
29
29
  if (this.cleanupHandler) {
30
30
  process.off("SIGINT", this.cleanupHandler);
31
31
  process.off("SIGTERM", this.cleanupHandler);
32
+ process.off("exit", this.cleanupHandler);
32
33
  }
33
34
  this.cleanupHandler = () => this.stopTunnel();
34
35
  process.on("SIGINT", this.cleanupHandler);
35
36
  process.on("SIGTERM", this.cleanupHandler);
37
+ // Windows: SIGINT/SIGTERM may not fire on Ctrl+C — use 'exit' as fallback
38
+ process.on("exit", this.cleanupHandler);
36
39
 
37
40
  // Read stderr to find tunnel URL, then keep draining to avoid SIGPIPE
38
41
  const reader = proc.stderr.getReader();
@@ -81,6 +84,7 @@ class TunnelService {
81
84
  if (this.cleanupHandler) {
82
85
  process.off("SIGINT", this.cleanupHandler);
83
86
  process.off("SIGTERM", this.cleanupHandler);
87
+ process.off("exit", this.cleanupHandler);
84
88
  this.cleanupHandler = null;
85
89
  }
86
90
  if (this.childProcess) {