@hienlh/ppm 0.7.22 → 0.7.23
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 +6 -0
- package/package.json +1 -1
- package/src/cli/commands/restart.ts +99 -40
- package/src/server/index.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.23] - 2026-03-22
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Restart from PPM terminal**: restart command now spawns a detached worker process so it survives when the old server (and its terminals) are killed — previously running `ppm restart` inside a PPM terminal would kill the restart process itself before the new server could be spawned, causing persistent 502
|
|
7
|
+
- **Restart server script path**: save server entry script path in `status.json` during `ppm start` so restart always uses the stable installed location instead of a potentially ephemeral `bunx` cache path
|
|
8
|
+
|
|
3
9
|
## [0.7.22] - 2026-03-22
|
|
4
10
|
|
|
5
11
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { readFileSync, writeFileSync, existsSync, openSync
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync, openSync } from "node:fs";
|
|
4
4
|
|
|
5
5
|
const PPM_DIR = resolve(homedir(), ".ppm");
|
|
6
6
|
const STATUS_FILE = resolve(PPM_DIR, "status.json");
|
|
@@ -28,62 +28,121 @@ export async function restartServer(options: { config?: string }) {
|
|
|
28
28
|
process.exit(1);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Resolve server script: prefer saved path (stable install), fall back to import.meta.dir
|
|
32
|
+
const savedScript = status.serverScript as string | undefined;
|
|
33
|
+
const serverScript = savedScript && existsSync(savedScript)
|
|
34
|
+
? savedScript
|
|
35
|
+
: resolve(import.meta.dir, "../../server/index.ts");
|
|
36
|
+
|
|
37
|
+
const { configService } = await import("../../services/config.service.ts");
|
|
38
|
+
configService.load(options.config);
|
|
39
|
+
const port = status.port as number ?? configService.get("port");
|
|
40
|
+
const host = status.host as string ?? configService.get("host");
|
|
41
|
+
|
|
31
42
|
// Write restarting flag so tunnel cleanup handler skips killing cloudflared
|
|
32
43
|
writeFileSync(RESTARTING_FLAG, "");
|
|
33
44
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
// Generate a self-contained restart worker script.
|
|
46
|
+
// This runs as a detached process so it survives even if the current process
|
|
47
|
+
// (and the PPM server hosting its terminal) is killed.
|
|
48
|
+
const params = JSON.stringify({
|
|
49
|
+
serverPid, port, host, serverScript,
|
|
50
|
+
config: options.config ?? "",
|
|
51
|
+
statusFile: STATUS_FILE,
|
|
52
|
+
pidFile: PID_FILE,
|
|
53
|
+
restartingFlag: RESTARTING_FLAG,
|
|
54
|
+
ppmDir: PPM_DIR,
|
|
55
|
+
});
|
|
41
56
|
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
const workerPath = resolve(PPM_DIR, ".restart-worker.ts");
|
|
58
|
+
writeFileSync(workerPath, `
|
|
59
|
+
import { readFileSync, writeFileSync, openSync, unlinkSync, appendFileSync, existsSync } from "node:fs";
|
|
60
|
+
import { createServer } from "node:net";
|
|
44
61
|
|
|
45
|
-
|
|
46
|
-
const { setDbProfile } = await import("../../services/db.service.ts");
|
|
47
|
-
if (options.config && /dev/i.test(options.config)) {
|
|
48
|
-
setDbProfile("dev");
|
|
49
|
-
}
|
|
62
|
+
const P = ${params};
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
async function main() {
|
|
65
|
+
const log = (level: string, msg: string) => {
|
|
66
|
+
const ts = new Date().toISOString();
|
|
67
|
+
try { appendFileSync(P.ppmDir + "/ppm.log", "[" + ts + "] [" + level + "] " + msg + "\\n"); } catch {}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Kill old server
|
|
71
|
+
try { process.kill(P.serverPid); log("INFO", "Restart: killed old server PID " + P.serverPid); } catch {}
|
|
56
72
|
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
// Wait for port to be free (up to 5s)
|
|
74
|
+
const start = Date.now();
|
|
75
|
+
while (Date.now() - start < 5000) {
|
|
76
|
+
const inUse: boolean = await new Promise((res) => {
|
|
77
|
+
const t = createServer()
|
|
78
|
+
.once("error", () => res(true))
|
|
79
|
+
.once("listening", () => { t.close(() => res(false)); })
|
|
80
|
+
.listen(P.port, P.host);
|
|
81
|
+
});
|
|
82
|
+
if (!inUse) break;
|
|
83
|
+
await Bun.sleep(200);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Spawn new server
|
|
87
|
+
const logFd = openSync(P.ppmDir + "/ppm.log", "a");
|
|
61
88
|
const child = Bun.spawn({
|
|
62
|
-
cmd: [
|
|
63
|
-
process.execPath, "run",
|
|
64
|
-
resolve(import.meta.dir, "../../server/index.ts"), "__serve__",
|
|
65
|
-
String(port), host, options.config ?? "",
|
|
66
|
-
],
|
|
89
|
+
cmd: [process.execPath, "run", P.serverScript, "__serve__", String(P.port), P.host, P.config],
|
|
67
90
|
stdio: ["ignore", logFd, logFd],
|
|
68
91
|
env: process.env,
|
|
69
92
|
});
|
|
70
93
|
child.unref();
|
|
71
94
|
|
|
72
|
-
// Update status with new
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
// Update status.json with new PID, keep tunnel info
|
|
96
|
+
try {
|
|
97
|
+
const status = JSON.parse(readFileSync(P.statusFile, "utf-8"));
|
|
98
|
+
status.pid = child.pid;
|
|
99
|
+
status.serverScript = P.serverScript;
|
|
100
|
+
writeFileSync(P.statusFile, JSON.stringify(status));
|
|
101
|
+
writeFileSync(P.pidFile, String(child.pid));
|
|
102
|
+
} catch {}
|
|
76
103
|
|
|
77
104
|
// Remove restarting flag
|
|
78
|
-
try { unlinkSync(
|
|
105
|
+
try { unlinkSync(P.restartingFlag); } catch {}
|
|
79
106
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
107
|
+
// Health check (up to 10s)
|
|
108
|
+
let ready = false;
|
|
109
|
+
const hStart = Date.now();
|
|
110
|
+
while (Date.now() - hStart < 10000) {
|
|
111
|
+
try {
|
|
112
|
+
const res = await fetch("http://127.0.0.1:" + P.port + "/api/health", { signal: AbortSignal.timeout(1000) });
|
|
113
|
+
if (res.ok) { ready = true; break; }
|
|
114
|
+
} catch {}
|
|
115
|
+
await Bun.sleep(300);
|
|
85
116
|
}
|
|
86
|
-
|
|
117
|
+
|
|
118
|
+
if (ready) {
|
|
119
|
+
log("INFO", "Restart complete — new server PID " + child.pid);
|
|
120
|
+
} else {
|
|
121
|
+
let alive = false;
|
|
122
|
+
try { process.kill(child.pid, 0); alive = true; } catch {}
|
|
123
|
+
log("ERROR", "Restart failed — server " + (alive ? "not responding on port " + P.port : "crashed on startup"));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Cleanup worker file
|
|
127
|
+
try { unlinkSync(P.ppmDir + "/.restart-worker.ts"); } catch {}
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
main();
|
|
132
|
+
`);
|
|
133
|
+
|
|
134
|
+
// Spawn worker as a fully detached process
|
|
135
|
+
const logFd = openSync(resolve(PPM_DIR, "ppm.log"), "a");
|
|
136
|
+
const worker = Bun.spawn({
|
|
137
|
+
cmd: [process.execPath, "run", workerPath],
|
|
138
|
+
stdio: ["ignore", logFd, logFd],
|
|
139
|
+
env: process.env,
|
|
140
|
+
});
|
|
141
|
+
worker.unref();
|
|
142
|
+
|
|
143
|
+
const { VERSION } = await import("../../version.ts");
|
|
144
|
+
console.log(`\n PPM v${VERSION} restarting... (worker PID: ${worker.pid})`);
|
|
145
|
+
console.log(` Server will restart in background. Check: ppm status\n`);
|
|
87
146
|
|
|
88
147
|
process.exit(0);
|
|
89
148
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -293,8 +293,8 @@ export async function startServer(options: {
|
|
|
293
293
|
process.exit(1);
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
// Write status file with both PIDs
|
|
297
|
-
const status = { pid: childPid, port, host, shareUrl, tunnelPid };
|
|
296
|
+
// Write status file with both PIDs + server script path for restart
|
|
297
|
+
const status = { pid: childPid, port, host, shareUrl, tunnelPid, serverScript: script };
|
|
298
298
|
writeFileSync(statusFile, JSON.stringify(status));
|
|
299
299
|
writeFileSync(pidFile, String(childPid));
|
|
300
300
|
|