heygen-com/hyperframes · error · InvalidProjectError

Not a directory: ${dir}

Error message

Not a directory: ${dir}

What it means

resolveProjectOrThrow: the resolved path (dirArg ?? '.', made absolute via resolve()) either does not exist (existsSync false) or exists but is not a directory (statSync().isDirectory() false). This is the generic not-a-directory guard that fires for any bad path that is not the special '#' case. Thrown as InvalidProjectError with title 'Not a directory: <abs path>'.

Source

Thrown at packages/cli/src/utils/project.ts:49

export function resolveProjectOrThrow(
  dirArg: string | undefined,
  options: ResolveProjectOptions = {},
): ProjectDir {
  const trimmed = dirArg?.trim();
  if (trimmed === "#") {
    throw new InvalidProjectError(
      "Invalid project directory: #",
      "# is a URL fragment, not a project path.",
      "Run hyperframes preview . from your project directory.",
    );
  }

  const dir = resolve(dirArg ?? ".");
  const name = basename(dir);
  const indexPath = resolve(dir, "index.html");

  if (!existsSync(dir) || !statSync(dir).isDirectory()) {
    throw new InvalidProjectError("Not a directory: " + dir);
  }
  if (options.requireIndex !== false && !existsSync(indexPath)) {
    throw new InvalidProjectError(
      "No composition found in " + dir,
      "No index.html file found.",
      "Run npx hyperframes init to create a new composition.",
    );
  }

  return { dir, name, indexPath };
}

export function resolveProject(
  dirArg: string | undefined,
  options: ResolveProjectOptions = {},
): ProjectDir {
  try {
    return resolveProjectOrThrow(dirArg, options);

View on GitHub (pinned to c2996c8626)

Solutions

  1. Verify the path exists and is a directory: `ls -ld <path>`.
  2. Pass the project directory (containing index.html), not the HTML file itself.
  3. Use an absolute path or run from inside the project with '.'.
  4. Check for typos, trailing spaces, or unexpanded ~ (use $HOME or the expanded path).

Example fix

# before: passed the html file, not the directory
hyperframes render ./index.html
# after
hyperframes render .
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync, statSync } from 'node:fs';
import { resolve } from 'node:path';

function ensureProjectDir(dirArg: string | undefined): string {
  const dir = resolve(dirArg ?? '.');
  if (!existsSync(dir) || !statSync(dir).isDirectory()) {
    throw new Error(`Not a directory: ${dir}`);
  }
  return dir;
}

Type guard

import { Stats } from 'node:fs';
function isDirectory(stats: Stats): boolean {
  return stats.isDirectory();
}

Try / catch

try {
  return resolveProjectOrThrow(dirArg);
} catch (err) {
  if (err instanceof InvalidProjectError && /^Not a directory:/.test(err.message)) {
    console.error(err.message, '— pass the project directory, not a file.');
  } else throw err;
}

Prevention

When it happens

Trigger: Passing a path to a file instead of a directory; a typo in the project path; a relative path resolved against an unexpected cwd; a path on a filesystem that is not mounted; a symlink loop or a broken symlink; passing '.' from a directory that itself was deleted.

Common situations: User runs `hyperframes render index.html` (file, not dir) instead of `hyperframes render .`; wrong cwd when using a relative path; a CI checkout step that did not clone the project; a path with a trailing slash or space that resolved wrong; a deleted/moved project folder.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/aff89b5a3e9689c1. Report an issue: GitHub.