fix(cli): validate project directory before starting preview (#1394)

Preview previously started Studio even when the path was invalid (e.g.
`hyperframes preview #`), yielding an empty project view. Align preview
with lint/render by resolving the project up front, and add a clearer
error when `#` is passed as a directory argument.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Manu Pareek
2026-06-12 20:28:48 -04:00
committed by GitHub
co-authored by Cursor
parent 7fa3696101
commit 2ec006297f
3 changed files with 95 additions and 19 deletions
+47
View File
@@ -0,0 +1,47 @@
import { describe, expect, it } from "vitest";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, basename } from "node:path";
import { InvalidProjectError, resolveProjectOrThrow } from "./project.js";
describe("resolveProjectOrThrow", () => {
it("rejects # as a project directory with a helpful message", () => {
try {
resolveProjectOrThrow("#");
expect.unreachable("expected InvalidProjectError");
} catch (err) {
expect(err).toBeInstanceOf(InvalidProjectError);
const error = err as InvalidProjectError;
expect(error.title).toBe("Invalid project directory: #");
expect(error.hint).toContain("URL fragment");
expect(error.suggestion).toContain("hyperframes preview .");
}
});
it("rejects a missing directory", () => {
const missing = join(tmpdir(), `hf-missing-${Date.now()}`);
expect(() => resolveProjectOrThrow(missing)).toThrowError(/Not a directory/);
});
it("rejects a directory without index.html", () => {
const dir = mkdtempSync(join(tmpdir(), "hf-empty-project-"));
try {
expect(() => resolveProjectOrThrow(dir)).toThrowError(/No composition found/);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
it("accepts a directory with index.html", () => {
const dir = mkdtempSync(join(tmpdir(), "hf-valid-project-"));
try {
writeFileSync(join(dir, "index.html"), '<html data-composition-id="test"></html>');
const project = resolveProjectOrThrow(dir);
expect(project.dir).toBe(dir);
expect(project.indexPath).toBe(join(dir, "index.html"));
expect(project.name).toBe(basename(dir));
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
});