create-caspian-app 1.4.6 → 1.5.0
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.
|
@@ -3,9 +3,12 @@ import { platform } from "os";
|
|
|
3
3
|
import { existsSync } from "fs";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { Socket } from "net";
|
|
6
|
+
import { randomBytes } from "crypto";
|
|
6
7
|
|
|
7
8
|
let pythonProcess: ChildProcess | null = null;
|
|
9
|
+
let pythonControlToken: string | null = null;
|
|
8
10
|
let isRestarting = false;
|
|
11
|
+
const GRACEFUL_SHUTDOWN_TIMEOUT_MS = 7000;
|
|
9
12
|
|
|
10
13
|
function isWindows(): boolean {
|
|
11
14
|
return platform() === "win32";
|
|
@@ -114,23 +117,68 @@ async function killProcessTree(child: ChildProcess): Promise<void> {
|
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
export function requestGracefulShutdown(
|
|
121
|
+
child: ChildProcess,
|
|
122
|
+
controlToken: string | null,
|
|
123
|
+
timeout = GRACEFUL_SHUTDOWN_TIMEOUT_MS,
|
|
124
|
+
): Promise<boolean> {
|
|
125
|
+
if (child.exitCode !== null) return Promise.resolve(true);
|
|
126
|
+
const stdin = child.stdin;
|
|
127
|
+
if (!controlToken || !stdin?.writable) return Promise.resolve(false);
|
|
128
|
+
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
let settled = false;
|
|
131
|
+
const finish = (stopped: boolean) => {
|
|
132
|
+
if (settled) return;
|
|
133
|
+
settled = true;
|
|
134
|
+
clearTimeout(timer);
|
|
135
|
+
child.off("exit", onExit);
|
|
136
|
+
child.off("close", onExit);
|
|
137
|
+
resolve(stopped);
|
|
138
|
+
};
|
|
139
|
+
const onExit = () => finish(true);
|
|
140
|
+
const timer = setTimeout(() => finish(false), timeout);
|
|
141
|
+
|
|
142
|
+
child.once("exit", onExit);
|
|
143
|
+
child.once("close", onExit);
|
|
144
|
+
stdin.write(`shutdown:${controlToken}\n`, (error) => {
|
|
145
|
+
if (error) finish(false);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function stopPythonProcess(
|
|
151
|
+
child: ChildProcess,
|
|
152
|
+
controlToken: string | null,
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
const stoppedGracefully = await requestGracefulShutdown(child, controlToken);
|
|
155
|
+
if (stoppedGracefully) return;
|
|
156
|
+
|
|
157
|
+
console.warn(
|
|
158
|
+
"Warning: Python server did not stop gracefully; forcing process termination.",
|
|
159
|
+
);
|
|
160
|
+
await killProcessTree(child);
|
|
161
|
+
}
|
|
162
|
+
|
|
117
163
|
function spawnPython(port: number, browserSyncPort?: number): ChildProcess {
|
|
118
164
|
const pythonPath = getVenvPythonPath();
|
|
119
165
|
const args = ["-u", "main.py"];
|
|
120
166
|
|
|
121
167
|
console.log(`-> Starting Python server on port ${port}...`);
|
|
122
168
|
|
|
169
|
+
pythonControlToken = randomBytes(32).toString("hex");
|
|
123
170
|
const env = {
|
|
124
171
|
...process.env,
|
|
125
172
|
PYTHONUNBUFFERED: "1",
|
|
126
173
|
PORT: String(port),
|
|
174
|
+
CASPIAN_DEV_CONTROL_TOKEN: pythonControlToken,
|
|
127
175
|
...(browserSyncPort
|
|
128
176
|
? { CASPIAN_BROWSER_SYNC_PORT: String(browserSyncPort) }
|
|
129
177
|
: {}),
|
|
130
178
|
};
|
|
131
179
|
|
|
132
180
|
const child = spawn(pythonPath, args, {
|
|
133
|
-
stdio: "inherit",
|
|
181
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
134
182
|
shell: false,
|
|
135
183
|
detached: !isWindows(),
|
|
136
184
|
env,
|
|
@@ -158,10 +206,12 @@ export async function restartPythonServer(
|
|
|
158
206
|
try {
|
|
159
207
|
console.log("-> Restarting Python server...");
|
|
160
208
|
const prev = pythonProcess;
|
|
209
|
+
const prevControlToken = pythonControlToken;
|
|
161
210
|
pythonProcess = null;
|
|
211
|
+
pythonControlToken = null;
|
|
162
212
|
|
|
163
213
|
if (prev) {
|
|
164
|
-
await
|
|
214
|
+
await stopPythonProcess(prev, prevControlToken);
|
|
165
215
|
await waitForPortRelease(port);
|
|
166
216
|
}
|
|
167
217
|
|
|
@@ -171,10 +221,12 @@ export async function restartPythonServer(
|
|
|
171
221
|
}
|
|
172
222
|
}
|
|
173
223
|
|
|
174
|
-
export function stopPythonServer(): void {
|
|
224
|
+
export async function stopPythonServer(): Promise<void> {
|
|
175
225
|
const prev = pythonProcess;
|
|
226
|
+
const prevControlToken = pythonControlToken;
|
|
176
227
|
pythonProcess = null;
|
|
177
|
-
|
|
228
|
+
pythonControlToken = null;
|
|
229
|
+
if (prev) await stopPythonProcess(prev, prevControlToken);
|
|
178
230
|
}
|
|
179
231
|
|
|
180
232
|
export async function waitForHttpHealth(
|