nexu-io/open-design · error · LocalDesignSystemImportError

BAD_REQUEST

BAD_REQUEST

Error message

local project path must be a directory

What it means

importLocalDesignSystemProject resolves the source path with realpath and stats it. If the result is not a directory the import cannot proceed, because the scanner walks a folder tree of project files. Files, broken symlinks, and missing paths are rejected as BAD_REQUEST.

Source

Thrown at apps/daemon/src/design-systems/import.ts:127

  'node_modules',
  'out',
  'target',
]);

const STYLE_EXTENSIONS = new Set(['.css', '.scss', '.sass', '.less']);
const COMPONENT_EXTENSIONS = new Set(['.tsx', '.jsx', '.vue', '.svelte']);
const ASSET_EXTENSIONS = new Set(['.svg', '.png', '.jpg', '.jpeg', '.webp', '.ico']);
const FONT_EXTENSIONS = new Set(['.woff', '.woff2', '.ttf', '.otf']);
const COMPONENT_NAMES = ['Button', 'Input', 'Card', 'Nav', 'Navbar', 'Sidebar'];
export async function importLocalDesignSystemProject(
  sourceRootInput: string,
  userDesignSystemsRoot: string,
  options: LocalDesignSystemImportOptions = {},
): Promise<LocalDesignSystemImportResult> {
  const sourceRoot = await realpath(sourceRootInput);
  const sourceStats = await stat(sourceRoot);
  if (!sourceStats.isDirectory()) {
    throw new LocalDesignSystemImportError('BAD_REQUEST', 'local project path must be a directory');
  }

  const scan = await scanProject(sourceRoot);
  const displayName = cleanDisplayName(options.name ?? scan.packageName ?? options.fallbackName ?? path.basename(sourceRoot));
  const id = await reserveNextAvailableSlug(userDesignSystemsRoot, slugify(displayName), options.reservedIds);
  const outDir = path.join(userDesignSystemsRoot, id);
  const importMode = normalizeImportMode(options.importMode);
  const craftApplies = normalizeCraftList(options.craftApplies);
  const now = options.now ?? new Date();

  const files = [
    'USAGE.md',
    'DESIGN.md',
    'tokens.css',
    'design-tokens.json',
    'tailwind-v4.css',
    'components.html',
    'components.manifest.json',

View on GitHub (pinned to 5be4028344)

Solutions

  1. Select the project's root directory, not a file inside it.
  2. Confirm the path exists and is a folder before importing.
  3. If the path is a symlink, ensure it resolves to a real directory.

Example fix

// before
await importLocalDesignSystemProject('/proj/design-system.json', outRoot);

// after
await importLocalDesignSystemProject('/proj', outRoot);
Defensive patterns

Strategy: validation

Validate before calling

import { stat } from 'node:fs/promises';

async function assertSourceDir(sourceRootInput: string): Promise<void> {
  const stats = await stat(sourceRootInput);
  if (!stats.isDirectory()) {
    throw new Error('local project path must be a directory');
  }
}

await assertSourceDir(sourceRootInput);

Type guard

async function isDirectoryPath(p: string): Promise<boolean> {
  try { return (await stat(p)).isDirectory(); } catch { return false; }
}

Try / catch

try {
  await importLocalDesignSystemProject(sourceRootInput, userRoot);
} catch (err) {
  if (isLocalDesignSystemImportError(err) && err.code === 'BAD_REQUEST' && /must be a directory/i.test(err.message)) {
    // prompt the user to pick the project folder, not a file
  } else throw err;
}

Prevention

When it happens

Trigger: Caller passed a path to a single file (a .json or .zip), a broken symlink, or a path that does not exist. realpath/stat resolves the path and isDirectory() returns false.

Common situations: Selecting a packaged file instead of the project folder; the folder was moved or deleted after selection; path is a symlink pointing to a file; typo in an absolute path.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/fff193ffafa3b508. Report an issue: GitHub.