@hienlh/ppm 0.8.17 → 0.8.18
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 +5 -0
- package/package.json +1 -1
- package/src/cli/commands/restart.ts +58 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.18] - 2026-03-24
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Restart reliability**: Kill orphan processes holding the port via `lsof`/`netstat` (cross-platform), not just the PID from status.json — fixes restart failures when old server process outlives its PID record
|
|
7
|
+
|
|
3
8
|
## [0.8.17] - 2026-03-24
|
|
4
9
|
|
|
5
10
|
### Changed
|
package/package.json
CHANGED
|
@@ -70,6 +70,7 @@ export async function restartServer(options: { config?: string }) {
|
|
|
70
70
|
writeFileSync(workerPath, `
|
|
71
71
|
import { readFileSync, writeFileSync, openSync, unlinkSync, appendFileSync } from "node:fs";
|
|
72
72
|
import { createServer } from "node:net";
|
|
73
|
+
import { spawnSync } from "node:child_process";
|
|
73
74
|
|
|
74
75
|
// Ignore SIGHUP — when we kill the old server, the terminal PTY dies and
|
|
75
76
|
// SIGHUP is sent to the entire process group. Without this, the worker
|
|
@@ -87,8 +88,26 @@ async function main() {
|
|
|
87
88
|
try { writeFileSync(P.resultFile, JSON.stringify({ ok, message: msg })); } catch {}
|
|
88
89
|
};
|
|
89
90
|
|
|
90
|
-
// Kill old server
|
|
91
|
+
// Kill old server PID
|
|
91
92
|
try { process.kill(P.serverPid); log("INFO", "Restart: killed old server PID " + P.serverPid); } catch {}
|
|
93
|
+
await Bun.sleep(500);
|
|
94
|
+
|
|
95
|
+
// Force-kill any process still holding the port (handles orphan/zombie processes)
|
|
96
|
+
const killByPort = () => {
|
|
97
|
+
try {
|
|
98
|
+
if (process.platform === "win32") {
|
|
99
|
+
const r = Bun.spawnSync(["cmd", "/c", "netstat -ano | findstr :" + P.port + " | findstr LISTENING"]);
|
|
100
|
+
const lines = r.stdout.toString().trim().split("\\n");
|
|
101
|
+
const pids = new Set(lines.map((l: string) => l.trim().split(/\\s+/).pop()).filter(Boolean));
|
|
102
|
+
for (const pid of pids) { try { process.kill(Number(pid)); } catch {} }
|
|
103
|
+
} else {
|
|
104
|
+
const r = Bun.spawnSync(["lsof", "-t", "-i", ":" + P.port]);
|
|
105
|
+
const pids = r.stdout.toString().trim().split("\\n").filter(Boolean);
|
|
106
|
+
for (const pid of pids) { try { process.kill(Number(pid)); } catch {} }
|
|
107
|
+
}
|
|
108
|
+
} catch {}
|
|
109
|
+
};
|
|
110
|
+
killByPort();
|
|
92
111
|
|
|
93
112
|
// Wait for port to be free (up to 5s)
|
|
94
113
|
const start = Date.now();
|
|
@@ -100,25 +119,51 @@ async function main() {
|
|
|
100
119
|
.listen(P.port, P.host);
|
|
101
120
|
});
|
|
102
121
|
if (!inUse) break;
|
|
122
|
+
killByPort();
|
|
103
123
|
await Bun.sleep(200);
|
|
104
124
|
}
|
|
105
125
|
|
|
106
|
-
// Spawn new server
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
// Spawn new server — on Windows use PowerShell Start-Process for true detach
|
|
127
|
+
// (Bun.spawn + unref on Windows keeps child in same job object → dies when worker exits)
|
|
128
|
+
let childPid: number;
|
|
129
|
+
const serverArgs = ["run", P.serverScript, "__serve__", String(P.port), P.host, P.config].filter(Boolean);
|
|
130
|
+
|
|
131
|
+
if (process.platform === "win32") {
|
|
132
|
+
const bunExe = process.execPath.replace(/\\\\/g, "\\\\\\\\");
|
|
133
|
+
const logPath = (P.ppmDir + "/ppm.log").replace(/\\//g, "\\\\").replace(/\\\\/g, "\\\\\\\\");
|
|
134
|
+
const errPath = (P.ppmDir + "/ppm.err.log").replace(/\\//g, "\\\\").replace(/\\\\/g, "\\\\\\\\");
|
|
135
|
+
const argStr = serverArgs.map((a: string) => "'" + (a || "_") + "'").join(",");
|
|
136
|
+
const psCmd = "$p = Start-Process -PassThru -WindowStyle Hidden"
|
|
137
|
+
+ " -FilePath '" + bunExe + "'"
|
|
138
|
+
+ " -ArgumentList " + argStr
|
|
139
|
+
+ " -RedirectStandardOutput '" + logPath + "'"
|
|
140
|
+
+ " -RedirectStandardError '" + errPath + "'"
|
|
141
|
+
+ "; Write-Output $p.Id";
|
|
142
|
+
const r = spawnSync("powershell", ["-NoProfile", "-Command", psCmd], { stdio: ["ignore", "pipe", "pipe"] });
|
|
143
|
+
childPid = parseInt(String(r.stdout).trim(), 10);
|
|
144
|
+
if (isNaN(childPid)) {
|
|
145
|
+
log("ERROR", "Failed to start server via PowerShell: " + String(r.stderr).trim());
|
|
146
|
+
writeResult(false, "Failed to start server on Windows");
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
const logFd = openSync(P.ppmDir + "/ppm.log", "a");
|
|
151
|
+
const child = Bun.spawn({
|
|
152
|
+
cmd: [process.execPath, ...serverArgs],
|
|
153
|
+
stdio: ["ignore", logFd, logFd],
|
|
154
|
+
env: process.env,
|
|
155
|
+
});
|
|
156
|
+
child.unref();
|
|
157
|
+
childPid = child.pid;
|
|
158
|
+
}
|
|
114
159
|
|
|
115
160
|
// Update status.json with new PID, keep tunnel info
|
|
116
161
|
try {
|
|
117
162
|
const status = JSON.parse(readFileSync(P.statusFile, "utf-8"));
|
|
118
|
-
status.pid =
|
|
163
|
+
status.pid = childPid;
|
|
119
164
|
status.serverScript = P.serverScript;
|
|
120
165
|
writeFileSync(P.statusFile, JSON.stringify(status));
|
|
121
|
-
writeFileSync(P.pidFile, String(
|
|
166
|
+
writeFileSync(P.pidFile, String(childPid));
|
|
122
167
|
} catch {}
|
|
123
168
|
|
|
124
169
|
// Remove restarting flag
|
|
@@ -147,7 +192,7 @@ async function main() {
|
|
|
147
192
|
} catch {}
|
|
148
193
|
|
|
149
194
|
if (ready) {
|
|
150
|
-
let msg = "Restart complete (PID: " +
|
|
195
|
+
let msg = "Restart complete (PID: " + childPid + ", port: " + P.port + ")";
|
|
151
196
|
if (shareUrl && tunnelPid) {
|
|
152
197
|
msg += tunnelAlive ? " — tunnel alive" : " — tunnel dead, run 'ppm stop && ppm start --share'";
|
|
153
198
|
}
|
|
@@ -155,7 +200,7 @@ async function main() {
|
|
|
155
200
|
writeResult(true, msg);
|
|
156
201
|
} else {
|
|
157
202
|
let alive = false;
|
|
158
|
-
try { process.kill(
|
|
203
|
+
try { process.kill(childPid, 0); alive = true; } catch {}
|
|
159
204
|
const msg = alive
|
|
160
205
|
? "Server started but not responding on port " + P.port + ". Check: ppm logs"
|
|
161
206
|
: "Server crashed on startup. Check: ppm logs";
|