StarForge

Registry System

How the Star Forge registry works, how blocks resolve dependencies, and how to add new components.

Registry System

Star Forge uses a custom registry that powers the CLI installation flow. The registry is the single source of truth for every component, primitive, and block that can be installed.

Never edit registry.json manually. It is generated by scripts/build-registry.js from TypeScript source files.


Source Files

The registry is built from two TypeScript files:

FilePurpose
src/registry/registry-ui.tsUI blocks (42 original + 15 base variants)
src/registry/registry-primitives.tsPrimitives (18 dual + 9 aliases + 4 UI helpers = 31)

Both export an array named ui and primitives respectively, typed against Registry from src/registry/schema.ts.


Registry Item Shape

interface RegistryItem {
  name: string;                  // e.g. "select-1", "base-select"
  type: 'registry:ui';
  dependencies?: string[];       // npm packages (e.g. ["lucide-react"])
  registryDependencies?: string[]; // Other registry items to auto-install
  files: Array<{ path: string; type: 'registry:ui' }>;
  example?: string;              // Preview component path
  component?: React.LazyExoticComponent<any>;
}

Dependency Resolution

When a user runs npx shadcn@latest add <url>, the CLI:

  1. Downloads the requested item from registry.json
  2. Reads registryDependencies
  3. Recursively resolves and installs each dependency
  4. Reads dependencies and installs npm packages

Example: select-1-base

{
  "name": "select-1-base",
  "registryDependencies": ["base-select"],
  "dependencies": []
}
{
  "name": "base-select",
  "registryDependencies": [],
  "dependencies": ["@base-ui/react"]
}

Result: select-1-base + base-select primitive + @base-ui/react package are all installed automatically.


Registry Aliases

Star Forge uses three naming patterns for primitives:

PatternExamplePurpose
Prefixradix-selectExplicit Radix UI variant
Prefixbase-selectExplicit Base UI variant
No prefixselectBackward-compatible alias (defaults to Radix)

Blocks reference the alias (select) in their registryDependencies. This ensures backward compatibility while allowing the user to override with the -base block variant.


Adding a New Block

Create the component

Create the block source in src/components/star-forge/<category>/<name>.tsx.

Create the preview

Create the demo in src/components/star-forge-preview/<category>/<name>.tsx.

Register the block

Add an entry to src/registry/registry-ui.ts:

{
  name: 'my-block',
  type: 'registry:ui',
  registryDependencies: ['button', 'select'],
  dependencies: ['lucide-react'],
  files: [{ path: 'src/components/star-forge/my-block.tsx', type: 'registry:ui' }],
  example: 'src/components/star-forge-preview/my-block.tsx',
  component: React.lazy(() =>
    import('@/components/star-forge-preview/my-block').then((mod) => ({
      default: mod.default
    }))
  )
}

Build the registry

npm run build:components

Verify

npm run audit:registry

Adding a Base UI Variant for an Existing Block

If a block depends on dual primitives and you want a Base UI install path:

  1. Create a new entry in src/registry/registry-ui.ts with -base suffix
  2. Change registryDependencies to use base-* primitives
  3. Keep the same files and example paths (the block code does not change)

Example:

{
  name: 'my-block-base',
  registryDependencies: ['base-button', 'base-select'],
  // ...same files and example as my-block
}
  1. Add hasEngineChoice={true} to the ComponentPreview or DrawerCodePreview in the MDX docs.

Build Pipeline

registry-ui.ts


parseRegistryItemsFromTsSource() ──► registry.json

registry-primitives.ts

scripts/build-registry.js uses the TypeScript compiler API to parse the AST of both registry files and extract all item metadata. It then writes a single registry.json.


Validation

Run the audit script after every registry change:

npm run audit:registry

This checks that every registryDependency points to an item that actually exists in registry.json.