mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-09 10:21:03 +00:00
fix(env): return empty body for missing .env files; surface non-OK responses cleanly (#767)
Previously, fetching the .env file for a stack with no env files at all returned a 404 with a JSON error body. The frontend's secondary loader (changeEnvFile) called res.text() without checking res.ok, which caused the error body to be stuffed directly into the editor as if it were file content. Two-part fix: Backend (routes/stacks.ts): - For the default GET /stacks/:name/env (no ?file= query) when the stack has no env files, respond 200 with an empty body and an X-Env-Exists: false header instead of 404. - For an explicit ?file= query that resolves to a missing file, keep the 404 (the caller asked for something specific). - Catch a TOCTOU ENOENT between access() and readFile() and return the same friendly empty-body shape, not a generic 500. Frontend (EditorLayout.tsx::changeEnvFile): - Check res.ok before reading the body. On a non-OK response, clear the editor content and surface a friendly toast instead of pasting the server's JSON error string into the file.
This commit is contained in:
@@ -185,7 +185,7 @@ stacksRouter.get('/:stackName/env', async (req: Request, res: Response) => {
|
||||
const requestedFile = req.query.file as string | undefined;
|
||||
const envPaths = await resolveAllEnvFilePaths(req.nodeId, stackName);
|
||||
|
||||
let envPath = envPaths[0];
|
||||
let envPath: string | undefined = envPaths[0];
|
||||
|
||||
if (requestedFile) {
|
||||
if (envPaths.includes(requestedFile)) {
|
||||
@@ -195,6 +195,15 @@ stacksRouter.get('/:stackName/env', async (req: Request, res: Response) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Default path with no env files yet: reply 200 with an empty body and a
|
||||
// header the frontend can read. This avoids surfacing a 404 for the
|
||||
// legitimate "stack has no .env yet" case, which previous flows
|
||||
// sometimes echoed back to the user as a confusing error string.
|
||||
if (!envPath) {
|
||||
res.setHeader('X-Env-Exists', 'false');
|
||||
return res.send('');
|
||||
}
|
||||
|
||||
const fsService = FileSystemService.getInstance(req.nodeId);
|
||||
|
||||
try {
|
||||
@@ -204,11 +213,31 @@ stacksRouter.get('/:stackName/env', async (req: Request, res: Response) => {
|
||||
if (code !== 'ENOENT') {
|
||||
console.error('[Sencho] Unexpected error checking env file existence:', (e as Error).message);
|
||||
}
|
||||
return res.status(404).json({ error: 'Env file not found' });
|
||||
// No env file at the resolved path. For an explicit ?file= query we
|
||||
// surface a 404 (the caller asked for something specific). Otherwise
|
||||
// treat it as the empty-stack case above.
|
||||
if (requestedFile) {
|
||||
return res.status(404).json({ error: 'Env file not found' });
|
||||
}
|
||||
res.setHeader('X-Env-Exists', 'false');
|
||||
return res.send('');
|
||||
}
|
||||
|
||||
const content = await fsService.readFile(envPath, 'utf-8');
|
||||
res.send(content);
|
||||
try {
|
||||
const content = await fsService.readFile(envPath, 'utf-8');
|
||||
res.setHeader('X-Env-Exists', 'true');
|
||||
return res.send(content);
|
||||
} catch (e: unknown) {
|
||||
// TOCTOU: the file existed at access() but vanished before readFile().
|
||||
// Return the same friendly empty-body shape rather than a generic 500
|
||||
// that the frontend would otherwise echo as an opaque error.
|
||||
const code = (e as NodeJS.ErrnoException)?.code;
|
||||
if (code === 'ENOENT' && !requestedFile) {
|
||||
res.setHeader('X-Env-Exists', 'false');
|
||||
return res.send('');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to read env file:', error);
|
||||
res.status(500).json({ error: 'Failed to read env file' });
|
||||
|
||||
@@ -1159,11 +1159,20 @@ export default function EditorLayout() {
|
||||
setIsFileLoading(true);
|
||||
try {
|
||||
const res = await apiFetch(`/stacks/${selectedFile}/env?file=${encodeURIComponent(file)}`);
|
||||
if (!res.ok) {
|
||||
// Don't stuff a JSON error body into the editor on a non-OK response.
|
||||
setEnvContent('');
|
||||
setOriginalEnvContent('');
|
||||
toast.error('Could not load env file');
|
||||
return;
|
||||
}
|
||||
const text = await res.text();
|
||||
setEnvContent(text || '');
|
||||
setOriginalEnvContent(text || '');
|
||||
} catch (e) {
|
||||
console.error('Failed to switch env file', e);
|
||||
setEnvContent('');
|
||||
setOriginalEnvContent('');
|
||||
} finally {
|
||||
setIsFileLoading(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user