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
- Select the project's root directory, not a file inside it.
- Confirm the path exists and is a folder before importing.
- 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
- Let the user pick via a directory chooser that disallows file selection.
- Stat the path and confirm isDirectory() before invoking the importer.
- Resolve symlinks before the check so broken links fail fast.
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
- Revision feedback is required
- BAD_REQUEST
- invalid JSON in ${filePath}: ${message}
- ${filePath} must contain a JSON object
- ARTIFACT_MANIFEST_INVALID
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/fff193ffafa3b508.
Report an issue: GitHub.