gentleduck/registry build
Generic index-building core with explicit extensions, type-safe config, and incremental rebuilds.
gentleduck/registry-build is a generic build core for index-like outputs. It can still power UI registries, but it can also drive package catalogs, Arch-style repository indexes, search manifests, and custom generated artifacts through explicit extensions.
What this package is
gentleduck/registry-build is a consumer-owned build system for indexable content.
Use it when you need to:
- declare one or more named collections of data or source trees
- attach structured metadata to those collections
- emit stable machine-readable indexes or repository artifacts
- generate derived outputs such as manifests, search indexes, component indexes, or theme files
- keep the build reproducible and consumer-owned instead of hardcoding repo paths
The package is intentionally split into a small core and explicit extensions. That keeps the base runner reusable while still supporting docs-site style builds through the compatibility layer.
Why it exists
The old approach to registry generation usually looks like this:
- arrays of entries hardcoded in source
- output paths hidden behind environment variables
- framework-specific logic baked into the build
- one repo layout assumed forever
registry-build replaces that with:
- a typed
registry-build.config.ts - a generic
collectionsmodel for non-UI consumers - path-aware config composition with
extends - explicit extension hooks for optional outputs
- CLI-driven execution
- incremental caching for repeated local builds
Architecture at a glance
Capability map
| Layer | Responsibility | Why it matters |
|---|---|---|
| Config loader | Discover config, resolve extends, normalize paths | Consumers own the build contract |
| Collections model | Materialize named datasets and source declarations | Non-UI consumers get a generic starting point |
| Compatibility layer | Translate legacy UI config into generic collection artifacts | Existing docs-style builds keep working while the core evolves |
| Built-in extensions | Index build, components, colors, component index, validation, banner | All processing is extension-driven, nothing runs by default |
| Custom extensions | Add repository indexes, manifests, search indexes, or any domain-specific output | Composable and explicit |
| CLI | Run builds from app/package roots | Works like a normal tool, not a repo-only script |
| Performance layer | File-hash cache, write skipping, changed-only mode, bounded concurrency | Fast warm builds and less filesystem churn |
Quick links
- Getting Started - Install the package and ship your first build
- Architecture - Build lifecycle, context, artifacts, and extension stages
- Configuration - Full config reference with collapsible sections
- Extensions - Built-in extensions and custom extension API
- CLI - Commands, flags, JSON output, and scripting patterns
- Performance - Cache behavior, changed-only mode, and warm builds
- Recipes - Concrete patterns for docs sites, manifests, and custom outputs
- Course - Guided walkthrough for building an Arch-style package index
- Testing and CI - Golden tests, pack smoke tests, and release checks
- Troubleshooting - Common failure modes and debugging paths
Installation
npm install @gentleduck/registry-build
npm install @gentleduck/registry-build
Core model
Consumer-owned config
The config file lives beside the app or package that owns the outputs. That means source paths, registry entries, output directories, extensions, and performance settings are all declared by the consumer, not buried inside the shared package.
Core phases vs extensions
The core runner is entirely extension-driven. Index building, component generation, validation, component-index generation, colors/themes, and banners are all extensions that consumers attach explicitly. If you do not attach them, they do not run.
Typed registry namespaces
If you are building a UI registry, item types are validated as registry:${string}. That same namespace carries through sources, targetPaths, package mappings, schema item types, and inline registry entries. Non-UI consumers can start from collections and avoid those compatibility fields entirely.
Collections-first model
For new non-UI builds, start with collections. A collection can hold file-backed data, source declarations, and arbitrary metadata that your extension reads later. That is the preferred mental model for package indexes, repository catalogs, and search manifests.
Incremental rebuild behavior
The builder stores cache data under <output.dir>/.registry-build/. On warm runs it reuses file hashes, skips unchanged writes, removes stale generated files, and can narrow work further with --changed-only plus explicit changed paths.
Typical use cases
- Build a docs-site registry from component source folders.
- Emit an installable JSON index for a CLI.
- Generate an Arch-like repository database and search manifest.
- Create a generated component import map for a framework.
- Generate theme and token payloads from static data files.
- Produce custom artifacts through extension hooks after the base registry is materialized.
Production checklist
- keep the config beside the output-owning app or package
- start new non-UI builds from
collectionsinstead of UI compatibility fields - declare every UI registry item type explicitly through the same
registry:${string}namespace - use extensions only for optional or logic-bearing outputs
- verify two consecutive warm builds produce zero rewritten files when nothing changed
- keep
.registry-build/ignored by Git - add typecheck, docs-content build, and at least one real consumer smoke test to CI