mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(studio): make optional file reads explicit (#883)
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { Hono } from "hono";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { registerFileRoutes } from "./files";
|
||||
import type { StudioApiAdapter } from "../types";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function createProjectDir(): string {
|
||||
const projectDir = mkdtempSync(join(tmpdir(), "hf-files-test-"));
|
||||
tempDirs.push(projectDir);
|
||||
writeFileSync(join(projectDir, "index.html"), "<html><body>Preview</body></html>");
|
||||
return projectDir;
|
||||
}
|
||||
|
||||
function createAdapter(projectDir: string): StudioApiAdapter {
|
||||
return {
|
||||
listProjects: () => [],
|
||||
resolveProject: async (id: string) => ({ id, dir: projectDir }),
|
||||
bundle: async () => null,
|
||||
lint: async () => ({ findings: [] }),
|
||||
runtimeUrl: "/api/runtime.js",
|
||||
rendersDir: () => "/tmp/renders",
|
||||
startRender: () => ({
|
||||
id: "job-1",
|
||||
status: "rendering",
|
||||
progress: 0,
|
||||
outputPath: "/tmp/out.mp4",
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
describe("registerFileRoutes", () => {
|
||||
it("returns empty content for missing files when caller marks the read optional", async () => {
|
||||
const projectDir = createProjectDir();
|
||||
const app = new Hono();
|
||||
registerFileRoutes(app, createAdapter(projectDir));
|
||||
|
||||
const response = await app.request(
|
||||
"http://localhost/projects/demo/files/missing-file.txt?optional=1",
|
||||
);
|
||||
const payload = (await response.json()) as { filename?: string; content?: string };
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(payload.filename).toBe("missing-file.txt");
|
||||
expect(payload.content).toBe("");
|
||||
});
|
||||
|
||||
it("still returns 404 for other missing files", async () => {
|
||||
const projectDir = createProjectDir();
|
||||
const app = new Hono();
|
||||
registerFileRoutes(app, createAdapter(projectDir));
|
||||
|
||||
const response = await app.request("http://localhost/projects/demo/files/missing-file.txt");
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
});
|
||||
@@ -26,7 +26,11 @@ import { removeElementFromHtml } from "../helpers/sourceMutation.js";
|
||||
* Returns null (and sends an error response) if anything is invalid.
|
||||
*/
|
||||
interface RouteContext {
|
||||
req: { param: (name: string) => string; path: string };
|
||||
req: {
|
||||
param: (name: string) => string;
|
||||
path: string;
|
||||
query: (name: string) => string | undefined;
|
||||
};
|
||||
json: (data: unknown, status?: number) => Response;
|
||||
}
|
||||
|
||||
@@ -135,9 +139,16 @@ export function registerFileRoutes(api: Hono, adapter: StudioApiAdapter): void {
|
||||
// ── Read ──
|
||||
|
||||
api.get("/projects/:id/files/*", async (c) => {
|
||||
const res = await resolveProjectFile(c, adapter, { mustExist: true });
|
||||
const res = await resolveProjectFile(c, adapter);
|
||||
if ("error" in res) return res.error;
|
||||
|
||||
if (!existsSync(res.absPath)) {
|
||||
if (c.req.query("optional") === "1") {
|
||||
return c.json({ filename: res.filePath, content: "" });
|
||||
}
|
||||
return c.json({ error: "not found" }, 404);
|
||||
}
|
||||
|
||||
const content = readFileSync(res.absPath, "utf-8");
|
||||
return c.json({ filename: res.filePath, content });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user