MCPcopy Index your code
hub / github.com/garrytan/gstack / handleReload

Function handleReload

design/src/serve.ts:184–235  ·  view source on GitHub ↗
(req: Request)

Source from the content-addressed store, hash-verified

182 }
183
184 async function handleReload(req: Request): Promise<Response> {
185 let body: any;
186 try {
187 body = await req.json();
188 } catch {
189 return Response.json({ error: "Invalid JSON" }, { status: 400 });
190 }
191
192 const newHtmlPath = body.html;
193 if (!newHtmlPath || !fs.existsSync(newHtmlPath)) {
194 return Response.json(
195 { error: `HTML file not found: ${newHtmlPath}` },
196 { status: 400 },
197 );
198 }
199
200 // Security: resolve symlinks and validate the reload path is a FILE
201 // inside the allowed directory (anchored to the initial HTML file's
202 // parent). Prevents path traversal via /api/reload reading arbitrary
203 // files. A path resolving to the allowedDir itself (a directory) used
204 // to pass the guard and then crash readFileSync with EISDIR — reject
205 // it explicitly with a clear 400 instead.
206 const resolvedReload = fs.realpathSync(path.resolve(newHtmlPath));
207 if (!resolvedReload.startsWith(allowedDir + path.sep)) {
208 return Response.json(
209 { error: `Path must be within: ${allowedDir}` },
210 { status: 403 },
211 );
212 }
213 if (!fs.statSync(resolvedReload).isFile()) {
214 return Response.json(
215 { error: `Path must be a file, not a directory: ${newHtmlPath}` },
216 { status: 400 },
217 );
218 }
219
220 // Swap the HTML content
221 htmlContent = fs.readFileSync(resolvedReload, "utf-8");
222 state = "serving";
223
224 console.error(`SERVE_RELOADED: html=${newHtmlPath}`);
225
226 // Reset timeout
227 if (timeoutTimer) clearTimeout(timeoutTimer);
228 timeoutTimer = setTimeout(() => {
229 console.error(`SERVE_TIMEOUT: after=${timeout}s`);
230 server.stop();
231 process.exit(1);
232 }, timeout * 1000);
233
234 return Response.json({ reloaded: true });
235 }
236
237 // Keep the process alive
238 await new Promise(() => {});

Callers 1

fetchFunction · 0.85

Calls 1

stopMethod · 0.80

Tested by

no test coverage detected